diff --git a/.gitignore b/.gitignore index 3275cbbdd..01a2cd1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -35,8 +35,10 @@ npm-debug.log yarn-error.log testem.log /typings -**/__tests__/e2e/**/*/platforms -**/__tests__/**/*/webpack.*.js +apps/**/*/*.js +apps/**/*/*.map +apps/**/*/platforms +apps/**/*/webpack.*.js *.tgz # System Files diff --git a/README.md b/README.md index cbbe36f7b..8cdd54ea4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,16 @@ npm run setup ## @nativescript/core ``` +npx apps:playground:ios +npm run apps:playground:android + + +npm run packages:core:build +npm run packages:core:test + + + + // livesync develop changes nx run core-e2e-playground:ios nx run core-e2e-playground:android diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 000000000..01442db36 --- /dev/null +++ b/apps/README.md @@ -0,0 +1,2 @@ +## Apps configured for workspace development + diff --git a/apps/automated/app/animation-frame/animation-frame.ts b/apps/automated/app/animation-frame/animation-frame.ts new file mode 100644 index 000000000..24efd8216 --- /dev/null +++ b/apps/automated/app/animation-frame/animation-frame.ts @@ -0,0 +1,85 @@ +import * as TKUnit from '../tk-unit'; +import * as animationFrame from '@nativescript/core/animation-frame'; +import * as fpsNative from '@nativescript/core/fps-meter/fps-native'; + +export function test_requestAnimationFrame_isDefined() { + TKUnit.assertNotEqual(animationFrame.requestAnimationFrame, undefined, 'Method animationFrame.requestAnimationFrame() should be defined!'); +} + +export function test_cancelAnimationFrame_isDefined() { + TKUnit.assertNotEqual(animationFrame.cancelAnimationFrame, undefined, 'Method animationFrame.cancelAnimationFrame() should be defined!'); +} + +export function test_requestAnimationFrame() { + let completed: boolean; + + const id = animationFrame.requestAnimationFrame(() => { + completed = true; + }); + + TKUnit.waitUntilReady(() => completed, 0.5, false); + animationFrame.cancelAnimationFrame(id); + TKUnit.assert(completed, 'Callback should be called!'); +} + +export function test_requestAnimationFrame_callbackCalledInCurrentFrame() { + let completed: boolean; + let currentFrameTime = 0; + const frameCb = new fpsNative.FPSCallback((time) => { + currentFrameTime = time; + }); + frameCb.start(); + + TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5); + let calledTime = 0; + animationFrame.requestAnimationFrame((frameTime) => { + calledTime = frameTime; + completed = calledTime >= frameTime; + }); + + TKUnit.waitUntilReady(() => completed, 0.5, false); + frameCb.stop(); + TKUnit.assert(completed, 'Callback should be called in current frame!'); +} + +export function test_requestAnimationFrame_nextCallbackCalledInNextFrame() { + let completed: boolean; + let currentFrameTime = 0; + const frameCb = new fpsNative.FPSCallback((time) => { + currentFrameTime = time; + }); + frameCb.start(); + + TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5); + animationFrame.requestAnimationFrame((firstFrameTime) => { + animationFrame.requestAnimationFrame((frameTime) => { + frameCb.stop(); + completed = frameTime > firstFrameTime && frameTime === currentFrameTime; + }); + }); + + TKUnit.waitUntilReady(() => completed, 0.5, false); + frameCb.stop(); + TKUnit.assert(completed, 'Callback should be called in next frame!'); +} + +export function test_requestAnimationFrame_shouldBeCancelled() { + let completed: boolean; + let currentFrameTime = 0; + const frameCb = new fpsNative.FPSCallback((time) => { + currentFrameTime = time; + }); + frameCb.start(); + + TKUnit.waitUntilReady(() => currentFrameTime > 0, 0.5); + animationFrame.requestAnimationFrame((firstFrameTime) => { + const cbId = animationFrame.requestAnimationFrame((frameTime) => { + completed = true; + }); + animationFrame.cancelAnimationFrame(cbId); + }); + + TKUnit.wait(1); + frameCb.stop(); + TKUnit.assert(!completed, 'Callback should not be called'); +} diff --git a/packages/core/__tests__/e2e/automated/app/app-root.xml b/apps/automated/app/app-root.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/app-root.xml rename to apps/automated/app/app-root.xml diff --git a/packages/core/__tests__/e2e/automated/app/app.css b/apps/automated/app/app.css similarity index 100% rename from packages/core/__tests__/e2e/automated/app/app.css rename to apps/automated/app/app.css diff --git a/apps/automated/app/app.ts b/apps/automated/app/app.ts new file mode 100644 index 000000000..2d2b00198 --- /dev/null +++ b/apps/automated/app/app.ts @@ -0,0 +1,146 @@ +let start; +if (typeof NSDate !== 'undefined') { + start = NSDate.date(); +} else { + start = java.lang.System.currentTimeMillis(); +} + +import { Application, ApplicationEventData, UnhandledErrorEventData, DiscardedErrorEventData, AndroidActivityBundleEventData, AndroidActivityEventData, AndroidApplication, AndroidActivityNewIntentEventData, AndroidActivityResultEventData, AndroidActivityBackPressedEventData } from '@nativescript/core'; + +if (Application.ios) { + // Observe application notifications. + Application.ios.addNotificationObserver(UIApplicationDidFinishLaunchingNotification, (notification: NSNotification) => { + console.log('UIApplicationDidFinishLaunchingNotification: ' + notification); + }); +} + +// Common events for both Android and iOS. +Application.on(Application.displayedEvent, function (args: ApplicationEventData) { + (global).isDisplayedEventFired = true; + + if (args.android) { + // For Android applications, args.android is an Android activity class. + console.log('Displayed Activity: ' + args.android); + } else if (args.ios) { + // For iOS applications, args.ios is UIApplication. + console.log('Displayed UIApplication: ' + args.ios); + } +}); + +Application.on(Application.launchEvent, function (args: ApplicationEventData) { + if (args.android) { + // For Android applications, args.android is an android.content.Intent class. + console.log('Launched Android application with the following intent: ' + args.android + '.'); + } else if (args.ios !== undefined) { + // For iOS applications, args.ios is NSDictionary (launchOptions). + console.log('Launched iOS application with options: ' + args.ios); + } +}); + +Application.on(Application.suspendEvent, function (args: ApplicationEventData) { + if (args.android) { + // For Android applications, args.android is an Android activity class. + console.log('Suspend Activity: ' + args.android); + } else if (args.ios) { + // For iOS applications, args.ios is UIApplication. + console.log('Suspend UIApplication: ' + args.ios); + } +}); + +Application.on(Application.resumeEvent, function (args: ApplicationEventData) { + if (args.android) { + // For Android applications, args.android is an Android activity class. + console.log('Resume Activity: ' + args.android); + } else if (args.ios) { + // For iOS applications, args.ios is UIApplication. + console.log('Resume UIApplication: ' + args.ios); + } +}); + +Application.on(Application.exitEvent, function (args: ApplicationEventData) { + if (args.android) { + // For Android applications, args.android is an Android activity class. + console.log('Exit Activity: ' + args.android); + } else if (args.ios) { + // For iOS applications, args.ios is UIApplication. + console.log('Exit UIApplication: ' + args.ios); + } +}); + +Application.on(Application.lowMemoryEvent, function (args: ApplicationEventData) { + if (args.android) { + // For Android applications, args.android is an Android activity class. + console.log('Low Memory: ' + args.android); + } else if (args.ios) { + // For iOS applications, args.ios is UIApplication. + console.log('Low Memory: ' + args.ios); + } +}); + +// Error events. +Application.on(Application.uncaughtErrorEvent, function (args: UnhandledErrorEventData) { + console.log('NativeScriptError: ' + args.error); + console.log((args.error).nativeException || (args.error).nativeError); + console.log((args.error).stackTrace || (args.error).stack); +}); + +Application.on(Application.discardedErrorEvent, function (args: DiscardedErrorEventData) { + console.log('[Discarded] NativeScriptError: ' + args.error); + console.log((args.error).nativeException || (args.error).nativeError); + console.log((args.error).stackTrace || (args.error).stack); +}); + +// Android activity events. +if (Application.android) { + Application.android.on(AndroidApplication.activityCreatedEvent, function (args: AndroidActivityBundleEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity + ', Bundle: ' + args.bundle); + }); + + Application.android.on(AndroidApplication.activityDestroyedEvent, function (args: AndroidActivityEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + }); + + Application.android.on(AndroidApplication.activityStartedEvent, function (args: AndroidActivityEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + }); + + Application.android.on(AndroidApplication.activityPausedEvent, function (args: AndroidActivityEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + }); + + Application.android.on(AndroidApplication.activityResumedEvent, function (args: AndroidActivityEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + }); + + Application.android.on(AndroidApplication.activityStoppedEvent, function (args: AndroidActivityEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + }); + + Application.android.on(AndroidApplication.saveActivityStateEvent, function (args: AndroidActivityBundleEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity + ', Bundle: ' + args.bundle); + }); + + Application.android.on(AndroidApplication.activityResultEvent, function (args: AndroidActivityResultEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity + ', requestCode: ' + args.requestCode + ', resultCode: ' + args.resultCode + ', Intent: ' + args.intent); + }); + + Application.android.on(AndroidApplication.activityBackPressedEvent, function (args: AndroidActivityBackPressedEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity); + // Set args.cancel = true to cancel back navigation and do something custom. + }); + + Application.android.on(AndroidApplication.activityNewIntentEvent, function (args: AndroidActivityNewIntentEventData) { + console.log('Event: ' + args.eventName + ', Activity: ' + args.activity + ', Intent: ' + args.intent); + }); +} + +let time; +if (typeof NSDate !== 'undefined') { + time = NSDate.date().timeIntervalSinceDate(start) * 1000; +} else { + time = java.lang.System.currentTimeMillis() - start; +} + +console.log(`TIME TO LOAD APP: ${time} ms`); + +Application.run({ moduleName: 'app-root' }); diff --git a/apps/automated/app/application-settings/application-settings-tests.ts b/apps/automated/app/application-settings/application-settings-tests.ts new file mode 100644 index 000000000..91bdfbf7a --- /dev/null +++ b/apps/automated/app/application-settings/application-settings-tests.ts @@ -0,0 +1,178 @@ +var appSettings = require('@nativescript/core/application-settings'); +import * as TKUnit from '../tk-unit'; + +var stringKey: string = 'stringKey'; +var boolKey: string = 'boolKey'; +var numberKey: string = 'numberKey'; +var noStringKey: string = 'noStringKey'; +var noBoolKey: string = 'noBoolKey'; +var noNumberKey: string = 'noNumberKey'; + +export var testBoolean = function () { + appSettings.setBoolean(boolKey, false); + var boolValueBefore = appSettings.getBoolean(boolKey); + TKUnit.assert(false === boolValueBefore, 'Cannot set boolean to false, currently it is: ' + appSettings.getBoolean(boolKey)); + + appSettings.setBoolean('boolKey', true); + var boolValue = appSettings.getBoolean('boolKey', false); + + TKUnit.assert(true === boolValue, 'Cannot set boolean to true'); + + TKUnit.assert(true === appSettings.getBoolean(boolKey), 'Cannot set boolean to true (no default)'); +}; + +export var testString = function () { + appSettings.setString('stringKey', 'String value'); + var stringValue = appSettings.getString('stringKey'); + + TKUnit.assert('String value' === stringValue, 'Cannot set string value'); +}; + +export var testNumber = function () { + appSettings.setNumber('numberKey', 54.321); + var value = parseFloat(appSettings.getNumber('numberKey').toFixed(3)); + + TKUnit.assert(54.321 === value, 'Cannot set number value 54.321 != ' + value); +}; + +export var testDefaults = function () { + var defaultValue = appSettings.getString('noStringKey', 'No string value'); + // will return "No string value" if there is no value for "noStringKey" + + TKUnit.assert('No string value' === defaultValue, 'Bad default string value'); + TKUnit.assert(true === appSettings.getBoolean(noBoolKey, true), 'Bad default boolean value'); + TKUnit.assert(123.45 === appSettings.getNumber(noNumberKey, 123.45), 'Bad default number value'); +}; + +export var testDefaultsWithNoDefaultValueProvided = function () { + var defaultValue = appSettings.getString('noStringKey'); + // will return undefined if there is no value for "noStringKey" + + TKUnit.assertEqual(defaultValue, undefined, 'Default string value is not undefined'); + + TKUnit.assertEqual(appSettings.getBoolean(noBoolKey), undefined, 'Default boolean value is not undefined'); + TKUnit.assertEqual(appSettings.getNumber(noNumberKey), undefined, 'Default number value is not undefined'); +}; + +export var testHasKey = function () { + var hasKey = appSettings.hasKey('noBoolKey'); + // will return false if there is no value for "noBoolKey" + + TKUnit.assert(!hasKey, 'There is a key: ' + noBoolKey); + TKUnit.assert(!appSettings.hasKey(noStringKey), 'There is a key: ' + noStringKey); + TKUnit.assert(!appSettings.hasKey(noNumberKey), 'There is a key: ' + noNumberKey); + + TKUnit.assert(appSettings.hasKey(boolKey), 'There is no key: ' + boolKey); + TKUnit.assert(appSettings.hasKey(stringKey), 'There is no key: ' + stringKey); + TKUnit.assert(appSettings.hasKey(numberKey), 'There is no key: ' + numberKey); +}; + +export var testRemove = function () { + appSettings.remove('boolKey'); + + TKUnit.assert(!appSettings.hasKey(boolKey), 'Failed to remove key: ' + boolKey); + + appSettings.remove(stringKey); + TKUnit.assert(!appSettings.hasKey(stringKey), 'Failed to remove key: ' + stringKey); + + appSettings.remove(numberKey); + TKUnit.assert(!appSettings.hasKey(numberKey), 'Failed to remove key: ' + numberKey); +}; + +export var testClear = function () { + appSettings.clear(); + + TKUnit.assert(!appSettings.hasKey(boolKey), 'Failed to remove key: ' + boolKey); + TKUnit.assert(!appSettings.hasKey(stringKey), 'Failed to remove key: ' + stringKey); + TKUnit.assert(!appSettings.hasKey(numberKey), 'Failed to remove key: ' + numberKey); +}; + +export var testFlush = function () { + appSettings.setString(stringKey, 'String value'); + + var flushed = appSettings.flush(); + // will return boolean indicating whether flush to disk was successful + + TKUnit.assert(flushed, 'Flush failed: ' + flushed); + TKUnit.assert(appSettings.hasKey(stringKey), 'There is no key: ' + stringKey); +}; + +export var testAllKeys = function () { + appSettings.setString(stringKey, 'String value'); + appSettings.setBoolean(boolKey, true); + appSettings.setNumber(numberKey, 22); + + var allKeys = appSettings.getAllKeys(); + TKUnit.assert(allKeys.indexOf(stringKey) !== -1, `${stringKey} is missing from .allKeys()`); + TKUnit.assert(allKeys.indexOf(boolKey) !== -1, `${boolKey} is missing from .allKeys()`); + TKUnit.assert(allKeys.indexOf(numberKey) !== -1, `${numberKey} is missing from .allKeys()`); +}; + +export var testInvalidKey = function () { + try { + appSettings.hasKey(undefined); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.hasKey(null); + TKUnit.assert(false, 'There is a key null'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.hasKey(123); + TKUnit.assert(false, 'There is a key number'); + } catch (e) { + // we should receive an exception here + } + + appSettings.hasKey('string'); +}; + +export var testInvalidValue = function () { + try { + appSettings.setBoolean(boolKey, 'str'); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.setBoolean(boolKey, 123); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.setString(boolKey, true); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.setString(boolKey, 123); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.setNumber(boolKey, true); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } + + try { + appSettings.setNumber(boolKey, '123'); + TKUnit.assert(false, 'There is a key undefined'); + } catch (e) { + // we should receive an exception here + } +}; diff --git a/packages/core/__tests__/e2e/automated/app/application-settings/application-settings.md b/apps/automated/app/application-settings/application-settings.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/application-settings/application-settings.md rename to apps/automated/app/application-settings/application-settings.md diff --git a/apps/automated/app/application/application-tests-common.ts b/apps/automated/app/application/application-tests-common.ts new file mode 100644 index 000000000..780c5c9f7 --- /dev/null +++ b/apps/automated/app/application/application-tests-common.ts @@ -0,0 +1,27 @@ +import * as app from '@nativescript/core/application'; +import { isAndroid, isIOS, Device, Application, platformNames } from '@nativescript/core'; + +import * as TKUnit from '../tk-unit'; + +if (isAndroid) { + console.log('We are running on an Android device!'); +} else if (isIOS) { + console.log('We are running on an iOS device!'); +} + +export function testInitialized() { + if (Device.os === platformNames.android) { + TKUnit.assert(app.android, 'Application module not properly intialized'); + } else if (Device.os === platformNames.ios) { + TKUnit.assert(app.ios, 'Application module not properly intialized'); + } +} + +export function testDisplayedEvent() { + // global.isDisplayedEventFired flag is set in app.ts application.displayedEvent handler + TKUnit.assert((global).isDisplayedEventFired, 'application.displayedEvent not fired'); +} + +export function testOrientation() { + TKUnit.assert(Application.orientation(), 'Orientation not initialized.'); +} diff --git a/apps/automated/app/application/application-tests.android.ts b/apps/automated/app/application/application-tests.android.ts new file mode 100644 index 000000000..8e8b9142c --- /dev/null +++ b/apps/automated/app/application/application-tests.android.ts @@ -0,0 +1,53 @@ +/* tslint:disable:no-unused-variable */ +import { Application, isAndroid } from '@nativescript/core'; +import * as TKUnit from '../tk-unit'; + +export * from './application-tests-common'; + +// >> application-app-android +var androidApp = Application.android; +// << application-app-android + +// >> application-app-android-context +var context = Application.android.context; +//// get the Files (Documents) folder (directory) +var dir = context.getFilesDir(); +// << application-app-android-context + +// >> application-app-android-current +if (androidApp.foregroundActivity === androidApp.startActivity) { + ////console.log("We are currently in the main (start) activity of the application"); +} +// << application-app-android-current + +// >> application-app-android-broadcast +//// Register the broadcast receiver +if (isAndroid) { + Application.android.registerBroadcastReceiver(android.content.Intent.ACTION_BATTERY_CHANGED, function onReceiveCallback(context: android.content.Context, intent: android.content.Intent) { + var level = intent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, -1); + var scale = intent.getIntExtra(android.os.BatteryManager.EXTRA_SCALE, -1); + var percent = (level / scale) * 100.0; + ////console.log("Battery: " + percent + "%"); + }); +} +//// When no longer needed, unregister the broadcast receiver +if (isAndroid) { + Application.android.unregisterBroadcastReceiver(android.content.Intent.ACTION_BATTERY_CHANGED); +} +// << application-app-android-broadcast + +export function testAndroidApplicationInitialized() { + TKUnit.assert(Application.android, 'Android application not initialized.'); + TKUnit.assert(Application.android.context, 'Android context not initialized.'); + TKUnit.assert(Application.android.foregroundActivity, 'Android foregroundActivity not initialized.'); + TKUnit.assert(Application.android.foregroundActivity.isNativeScriptActivity, 'Android foregroundActivity.isNativeScriptActivity is false.'); + TKUnit.assert(Application.android.startActivity, 'Android startActivity not initialized.'); + TKUnit.assert(Application.android.nativeApp, 'Android nativeApp not initialized.'); + TKUnit.assert(Application.android.orientation, 'Android orientation not initialized.'); + TKUnit.assert(Application.android.packageName, 'Android packageName not initialized.'); + TKUnit.assert(Application.android.systemAppearance, 'Android system appearance not initialized.'); +} + +export function testSystemAppearance() { + TKUnit.assert(Application.android.systemAppearance, 'System appearance not initialized.'); +} diff --git a/apps/automated/app/application/application-tests.d.ts b/apps/automated/app/application/application-tests.d.ts new file mode 100644 index 000000000..8030a5730 --- /dev/null +++ b/apps/automated/app/application/application-tests.d.ts @@ -0,0 +1,2 @@ +import * as android from './application-tests.android'; +import * as iOS from './application-tests.ios'; diff --git a/apps/automated/app/application/application-tests.ios.ts b/apps/automated/app/application/application-tests.ios.ts new file mode 100644 index 000000000..bfa054a7e --- /dev/null +++ b/apps/automated/app/application/application-tests.ios.ts @@ -0,0 +1,64 @@ +import { Application, Utils, isIOS } from '@nativescript/core'; +import * as TKUnit from '../tk-unit'; + +export * from './application-tests-common'; + +// >> application-ios-observer +//// Add the notification observer +if (Application.ios) { + var observer = Application.ios.addNotificationObserver(UIDeviceBatteryLevelDidChangeNotification, function onReceiveCallback(notification: NSNotification) { + var percent = UIDevice.currentDevice.batteryLevel * 100; + var message = 'Battery: ' + percent + '%'; + ////console.log(message); + }); +} +//// When no longer needed, remove the notification observer +if (isIOS) { + Application.ios.removeNotificationObserver(observer, UIDeviceBatteryLevelDidChangeNotification); +} +// << application-ios-observer + +// >> application-ios-delegate +//// Add custom application delegate +if (isIOS) { + class MyDelegate extends UIResponder implements UIApplicationDelegate { + public static ObjCProtocols = [UIApplicationDelegate]; + + applicationDidFinishLaunchingWithOptions(application: UIApplication, launchOptions: NSDictionary): boolean { + return true; + } + + applicationDidBecomeActive(application: UIApplication): void { + // Get reference to the application window. + //console.log("keyWindow: " + application.keyWindow); + } + } + + Application.ios.delegate = MyDelegate; +} + +// << application-ios-delegate + +export function testIOSApplicationInitialized() { + TKUnit.assert(Application.ios, 'iOS application not initialized.'); + TKUnit.assert(Application.ios.delegate, 'iOS delegate not initialized.'); + TKUnit.assert(Application.ios.nativeApp, 'iOS nativeApp not initialized.'); + TKUnit.assert(Application.ios.orientation, 'iOS orientation not initialized.'); + + if (Utils.ios.MajorVersion <= 11) { + TKUnit.assertNull(Application.ios.systemAppearance, 'iOS system appearance should be `null` on iOS <= 11.'); + } else { + TKUnit.assert(Application.ios.systemAppearance, 'iOS system appearance not initialized.'); + } + + TKUnit.assert(Application.ios.window, 'iOS window not initialized.'); + TKUnit.assert(Application.ios.rootController, 'iOS root controller not initialized.'); +} + +export function testSystemAppearance() { + if (Utils.ios.MajorVersion <= 11) { + TKUnit.assertNull(Application.ios.systemAppearance, 'System appearance should be `null` on iOS <= 11.'); + } else { + TKUnit.assert(Application.ios.systemAppearance, 'System appearance not initialized.'); + } +} diff --git a/packages/core/__tests__/e2e/automated/app/assets/logo.png b/apps/automated/app/assets/logo.png similarity index 100% rename from packages/core/__tests__/e2e/automated/app/assets/logo.png rename to apps/automated/app/assets/logo.png diff --git a/packages/core/__tests__/e2e/automated/app/assets/small-image.png b/apps/automated/app/assets/small-image.png similarity index 100% rename from packages/core/__tests__/e2e/automated/app/assets/small-image.png rename to apps/automated/app/assets/small-image.png diff --git a/packages/core/__tests__/e2e/automated/app/assets/splashscreen.png b/apps/automated/app/assets/splashscreen.png similarity index 100% rename from packages/core/__tests__/e2e/automated/app/assets/splashscreen.png rename to apps/automated/app/assets/splashscreen.png diff --git a/packages/core/__tests__/e2e/automated/app/assets/test-icon.png b/apps/automated/app/assets/test-icon.png similarity index 100% rename from packages/core/__tests__/e2e/automated/app/assets/test-icon.png rename to apps/automated/app/assets/test-icon.png diff --git a/apps/automated/app/color/color-tests.ts b/apps/automated/app/color/color-tests.ts new file mode 100644 index 000000000..6f55c871e --- /dev/null +++ b/apps/automated/app/color/color-tests.ts @@ -0,0 +1,91 @@ +// >> color-require +import * as colorModule from '@nativescript/core/color'; +var Color = colorModule.Color; +// << color-require +import * as TKUnit from '../tk-unit'; + +export var test_Hex_Color = function () { + // >> color-hex + // Creates the red color + var color = new Color('#FF0000'); + // << color-hex + TKUnit.assertEqual(color.a, 255, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 0, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 0, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#FF0000', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0xffff0000, 'Color.argb not properly parsed'); +}; + +export var test_ShortHex_Color = function () { + // >> color-hex-short + // Creates the color #FF8800 + var color = new Color('#F80'); + // << color-hex-short + TKUnit.assertEqual(color.a, 255, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 136, 'Color.g not properly parsed'); // 0x88 == 136 + TKUnit.assertEqual(color.b, 0, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#FF8800', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0xffff8800, 'Color.argb not properly parsed'); +}; + +export var test_Argb_Color = function () { + // >> color-rgb + // Creates the color with 100 alpha, 255 red, 100 green, 100 blue + var color = new Color(100, 255, 100, 100); + // << color-rgb + TKUnit.assertEqual(color.a, 100, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 100, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 100, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#64FF6464', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0x64ff6464, 'Color.argb not properly parsed'); +}; + +export var test_ArgbInt_Color = function () { + // >> color-rgb-single + // Creates the color with 100 alpha, 100 red, 100 green, 100 blue + var color = new Color(0x64646464); + // << color-rgb-single + TKUnit.assertEqual(color.a, 100, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 100, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 100, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 100, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#64646464', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0x64646464, 'Color.argb not properly parsed'); +}; + +export var test_rgb_Color_CSS = function () { + // + // ### Creating a Color from four RGB values + // ``` JavaScript + // Creates the color with 255 red, 100 green, 100 blue + var color = new Color('rgb(255, 100, 100)'); + // ``` + // + TKUnit.assertEqual(color.a, 255, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 100, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 100, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#FF6464', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0xffff6464, 'Color.argb not properly parsed'); +}; + +export var test_rgba_Color_CSS = function () { + var alpha = 0.5; + var expected = 0x80; + // + // ### Creating a Color from four RGB values + // ``` JavaScript + // Creates the color with 255 red, 100 green, 100 blue and 0 alpha + var color = new Color(`rgba(255, 100, 100, ${alpha})`); + // ``` + // + TKUnit.assertEqual(color.a, expected, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 100, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 100, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#80FF6464', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0x80ff6464, 'Color.argb not properly parsed'); +}; diff --git a/packages/core/__tests__/e2e/automated/app/color/color.md b/apps/automated/app/color/color.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/color/color.md rename to apps/automated/app/color/color.md diff --git a/apps/automated/app/connectivity/connectivity-tests.ts b/apps/automated/app/connectivity/connectivity-tests.ts new file mode 100644 index 000000000..7e756b5f2 --- /dev/null +++ b/apps/automated/app/connectivity/connectivity-tests.ts @@ -0,0 +1,40 @@ +// >> connectivity-require +import * as connectivity from '@nativescript/core/connectivity'; +// << connectivity-require + +export var test_DummyTestForSnippetOnly0 = function () { + // >> connectivity-type + var connectionType = connectivity.getConnectionType(); + switch (connectionType) { + case connectivity.connectionType.none: + //console.log("No connection"); + break; + case connectivity.connectionType.wifi: + //console.log("WiFi connection"); + break; + case connectivity.connectionType.mobile: + //console.log("Mobile connection"); + break; + } + // << connectivity-type +}; + +export var test_DummyTestForSnippetOnly1 = function () { + // >> connectivity-monitoring + connectivity.startMonitoring(function onConnectionTypeChanged(newConnectionType: number) { + switch (newConnectionType) { + case connectivity.connectionType.none: + //console.log("Connection type changed to none."); + break; + case connectivity.connectionType.wifi: + //console.log("Connection type changed to WiFi."); + break; + case connectivity.connectionType.mobile: + //console.log("Connection type changed to mobile."); + break; + } + }); + //... + connectivity.stopMonitoring(); + // << connectivity-monitoring +}; diff --git a/packages/core/__tests__/e2e/automated/app/connectivity/connectivity.md b/apps/automated/app/connectivity/connectivity.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/connectivity/connectivity.md rename to apps/automated/app/connectivity/connectivity.md diff --git a/apps/automated/app/console/console-tests.ts b/apps/automated/app/console/console-tests.ts new file mode 100644 index 000000000..cae887449 --- /dev/null +++ b/apps/automated/app/console/console-tests.ts @@ -0,0 +1,36 @@ +export var test_DummyTestForSnippetOnly0 = function () { + // >> console-log + console.log('Hello, world!'); + console.info('I am NativeScript'); + console.warn('Low memory'); + console.error('Uncaught Application Exception'); + // << console-log +}; + +export var test_DummyTestForSnippetOnly1 = function () { + // >> console-time + console.time('LoadTime'); + // << console-time + // >> console-timeend + console.timeEnd('LoadTime'); + // << console-timeend +}; + +export var test_DummyTestForSnippetOnly2 = function () { + // >> console-assert + console.assert(2 === 2, '2 equals 2'); + // << console-assert +}; + +export var test_DummyTestForSnippetOnly3 = function () { + // >> console-dir + var obj = { name: 'John', age: 34 }; + console.dir(obj); + // << console-dir +}; + +export var test_DummyTestForSnippetOnly5 = function () { + // >> console-trace + console.trace(); + // << console-trace +}; diff --git a/packages/core/__tests__/e2e/automated/app/console/console.md b/apps/automated/app/console/console.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/console/console.md rename to apps/automated/app/console/console.md diff --git a/apps/automated/app/data/observable-array-tests.ts b/apps/automated/app/data/observable-array-tests.ts new file mode 100644 index 000000000..3c4d0e8b9 --- /dev/null +++ b/apps/automated/app/data/observable-array-tests.ts @@ -0,0 +1,669 @@ +import * as TKUnit from '../tk-unit'; +// >> observable-array-require +import { Label, ObservableArray, ChangedData, ChangeType } from '@nativescript/core'; +// << observable-array-require + +export const test_ObservableArray_shouldCopySourceArrayItems = function () { + // >> observable-array-create + const sa = [1, 2, 3]; + const array = new ObservableArray(sa); + // << observable-array-create + + TKUnit.assertEqual(array.length, 3, 'ObservableArray length should be 3'); + TKUnit.assertEqual(sa.length, array.length, 'ObservableArray should copy all source array items!'); +}; + +export const test_ObservableArray_shouldCopyMultipleItemsAsSource = function () { + // // >> observable-array-arguments + // const array = new ObservableArray(1, 2, 3); + // // << observable-array-arguments + + // TKUnit.assertEqual(array.length, 3, "ObservableArray length should be 3"); + // TKUnit.assertEqual(array.getItem(1), 2, "ObservableArray should copy multiple items from source!"); + TKUnit.assertEqual(true, true); +}; + +export const test_ObservableArray_shouldCreateArrayFromSpecifiedLength = function () { + // >> observable-array-length + const array = new ObservableArray(100); + // << observable-array-length + + TKUnit.assertEqual(array.length, 100, 'ObservableArray should create array from specified length!'); +}; + +export const test_ObservableArray_shouldBeAbleToSetLength = function () { + // >> observable-array-newvalue + const array = new ObservableArray(100); + // >> (hide) + TKUnit.assertEqual(array.length, 100, 'ObservableArray should create array from specified length!'); + // << (hide) + array.length = 50; + // << observable-array-newvalue + + TKUnit.assertEqual(array.length, 50, 'ObservableArray should respect new length!'); +}; + +export const test_ObservableArray_getItemShouldReturnCorrectItem = function () { + // >> observable-array-getitem + const array = new ObservableArray([1, 2, 3]); + const firstItem = array.getItem(0); + const secondItem = array.getItem(1); + const thirdItem = array.getItem(2); + // << observable-array-getitem + + TKUnit.assert(firstItem === 1 && secondItem === 2 && thirdItem === 3, 'ObservableArray getItem() should return correct item!'); +}; + +export const test_ObservableArray_setItemShouldSetCorrectItem = function () { + // >> observable-array-setitem + const array = new ObservableArray([1, 2, 3]); + array.setItem(1, 5); + // << observable-array-setitem + TKUnit.assert(array.getItem(1) === 5, 'ObservableArray setItem() should set correct item!'); +}; + +export const test_ObservableArray_setItemShouldRaiseCorrectEvent = function () { + // >> observable-array-eventdata + let index: number; + let action: string; + let addedCount: number; + let removed: Array; + + const array = new ObservableArray([1, 2, 3]); + array.on('change', (args) => { + index = args.index; // Index of the changed item. + action = args.action; // Action. In this case Update. + addedCount = args.addedCount; // Number of added items. In this case 1. + removed = args.removed; // Array of removed items. In this case with single item (2). + }); + array.setItem(1, 5); + // << observable-array-eventdata + TKUnit.assertEqual(index, 1); + TKUnit.assertEqual(action, ChangeType.Update); + TKUnit.assertEqual(addedCount, 1); + TKUnit.assertEqual(removed[0], 2); +}; + +export const test_ObservableArray_concatShouldReturnNewArrayWithNewItemsAtTheEnd = function () { + // >> observable-array-combine + const array = new ObservableArray([1, 2, 3]); + const result = array.concat([4, 5, 6]); + // << observable-array-combine + TKUnit.assert(result.length === 6 && result[4] === 5, 'ObservableArray concat() should add items at the end!'); +}; + +export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWithComma = function () { + // >> observable-array-join + const array = new ObservableArray([1, 2, 3]); + const result = array.join(); + // << observable-array-join + TKUnit.assert(result === '1,2,3', 'ObservableArray join() should return string with all items separated with comma!'); +}; + +export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWithDot = function () { + // >> observable-array-join-separator + const array = new ObservableArray([1, 2, 3]); + const result = array.join('.'); + // << observable-array-join-separator + TKUnit.assert(result === '1.2.3', 'ObservableArray join() should return string with all items separated with dot!'); +}; + +export const test_ObservableArray_popShouldRemoveTheLastElement = function () { + // >> observable-array-join-pop + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.pop(); + // << observable-array-join-pop + TKUnit.assert(result === 3 && array.length === 2, 'ObservableArray pop() should remove last element!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_popShouldRemoveTheLastElementAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-join-change + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const index = array.length - 1; + // << (hide) + + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "delete". + // args.index is equal to the array length - 1. + // args.removed.length is 1. + // args.addedCount is 0. + + // >> (hide) + result = args; + // << (hide) + }); + + array.pop(); + // << observable-array-join-change + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Delete && result.removed.length === 1 && result.index === index && result.addedCount === 0, "ObservableArray pop() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_pushShouldAppendNewElement = function () { + // >> observable-array-push + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.push(4); + // << observable-array-push + TKUnit.assert(result === 4 && array.getItem(3) === 4, 'ObservableArray push() should append new element!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_pushShouldAppendNewElementAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-change-push + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "add". + // args.index is equal to the array length. + // args.removed.length is 0. + // args.addedCount is 1. + + // >> (hide) + result = args; + // << (hide) + }); + + array.push(4); + // << observable-array-change-push + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 1, "ObservableArray push() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_pushShouldAppendNewElements = function () { + // >> observable-array-push-multiple + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.push(4, 5, 6); + // << observable-array-push-multiple + TKUnit.assert(result === 6 && array.getItem(5) === 6, 'ObservableArray push() should append new elements!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_pushShouldAppendNewElementsAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-push-multiple-info + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "add". + // args.index is equal to the array length. + // args.removed.length is 0. + // args.addedCount is equal to the number of added items. + + // >> (hide) + result = args; + // << (hide) + }); + + array.push(4, 5, 6); + // << observable-array-push-multiple-info + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 3, "ObservableArray push() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArray = function () { + // >> observable-array-push-source + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.push([4, 5, 6]); + // << observable-array-push-source + TKUnit.assert(result === 6 && array.getItem(5) === 6, 'ObservableArray push() should append new elements from source array!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArrayAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-push-source-info + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "add". + // args.index is equal to the array length. + // args.removed.length is 0. + // args.addedCount is equal to the number of added items. + + // >> (hide) + result = args; + // << (hide) + }); + + array.push([4, 5, 6]); + // << observable-array-push-source-info + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 3, "ObservableArray push() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_reverseShouldReturnNewReversedArray = function () { + // >> observable-array-reverse + const array = new ObservableArray([1, 2, 3]); + const result = array.reverse(); + // << observable-array-reverse + TKUnit.assert(result.length === 3 && result[0] === 3, 'ObservableArray reverse() should return new reversed array!'); +}; + +export const test_ObservableArray_shiftShouldRemoveTheFirstElement = function () { + // >> observable-array-shift + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.shift(); + // << observable-array-shift + TKUnit.assert(result === 1 && array.length === 2, 'ObservableArray shift() should remove first element!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_shiftShouldRemoveTheFirstElementAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-shift-change + const array = new ObservableArray([1, 2, 3]); + + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "delete". + // args.index is 0. + // args.removed.length is 1. + // args.addedCount is 0. + + // >> (hide) + result = args; + // << (hide) + }); + + array.shift(); + // << observable-array-shift-change + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Delete && result.removed.length === 1 && result.index === 0 && result.addedCount === 0, "ObservableArray shift() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_sliceShouldReturnSectionAsNewArray = function () { + // >> observable-array-slice + const array = new ObservableArray([1, 2, 3]); + const result = array.slice(); + // << observable-array-slice + TKUnit.assert(result[2] === 3 && result.length === 3, 'ObservableArray slice() should return section!'); +}; + +export const test_ObservableArray_sliceWithParamsShouldReturnSectionAsNewArray = function () { + // >> observable-array-slice-args + const array = new ObservableArray([1, 2, 3, 4, 5]); + const result = array.slice(2, 4); + // << observable-array-slice-args + TKUnit.assert(result[1] === 4 && result.length === 2, 'ObservableArray slice() should return section according to specified arguments!'); +}; + +export const test_ObservableArray_sortShouldReturnNewSortedArray = function () { + // >> observable-array-sort + const array = new ObservableArray([3, 2, 1]); + const result = array.sort(); + // << observable-array-sort + TKUnit.assert(result[0] === 1 && result.length === 3, 'ObservableArray sort() should return new sorted array!'); +}; + +export const test_ObservableArray_sortShouldReturnNewSortedArrayAccordingSpecifiedOrder = function () { + // >> observable-array-sort-comparer + const array = new ObservableArray([10, 100, 1]); + const result = array.sort((a: number, b: number) => a - b); + // << observable-array-sort-comparer + TKUnit.assert(result[2] === 100 && result.length === 3, 'ObservableArray sort() should return new sorted array according to specified order!'); +}; + +export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsStartingFromSpecifiedIndex = function () { + // >> observable-array-splice + const array = new ObservableArray(['one', 'two', 'three']); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.splice(1, 2); + // << observable-array-splice + TKUnit.assert(result.length === 2 && result[0] === 'two' && array.length === 1 && array.getItem(0) === 'one', 'ObservableArray splice() should remove specified number of elements starting from specified index!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsStartingFromSpecifiedIndexAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-splice-change + const array = new ObservableArray([1, 2, 3]); + + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "splice". + // args.index is the start index. + // args.removed.length is equal to the number of deleted items. + // args.addedCount is 0. + + // >> (hide) + result = args; + // << (hide) + }); + + array.splice(1, 2); + // << observable-array-splice-change + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Splice && result.removed.length === 2 && result.index === 1 && result.addedCount === 0, "ObservableArray splice() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_spliceShouldInsertNewItemsInPlaceOfRemovedItemsStartingFromSpecifiedIndex = function () { + // >> observable-array-splice-args + const array = new ObservableArray(['one', 'two', 'three']); + const result = array.splice(1, 2, 'six', 'seven'); + // << observable-array-splice-args + TKUnit.assert(result.length === 2 && result[0] === 'two' && array.length === 3 && array.getItem(2) === 'seven', 'ObservableArray splice() should insert new items in place of removed!'); +}; + +export const test_ObservableArray_spliceShouldRemoveAndInertSpecifiedNumberOfElementsStartingFromSpecifiedIndexAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-splice-args-change + const array = new ObservableArray(['one', 'two', 'three']); + + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "splice". + // args.index is the start index. + // args.removed.length is equal to the number of deleted items. + // args.addedCount is equal to the amount of added and replaced items. + + // >> (hide) + result = args; + // << (hide) + }); + + array.splice(1, 2, 'six', 'seven', 'eight'); + // << observable-array-splice-args-change + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Splice && result.removed.length === 2 && result.index === 1 && result.addedCount === 3, "ObservableArray splice() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStart = function () { + // >> observable-array-unshift + const array = new ObservableArray([1, 2, 3]); + // >> (hide) + const viewBase = new Label(); + viewBase.set('testProperty', 0); + viewBase.bind({ sourceProperty: 'length', targetProperty: 'testProperty' }, array); + // << (hide) + const result = array.unshift(4, 5); + // << observable-array-unshift + + TKUnit.assert(array.getItem(0) === 4 && result === 5 && array.length === 5, 'ObservableArray unshift() should insert new elements from the start!'); + TKUnit.assert(viewBase.get('testProperty') === array.length, 'Expected: ' + array.length + ', Actual: ' + viewBase.get('testProperty')); +}; + +export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStartAndRaiseChangeEventWithCorrectArgs = function () { + let result: ChangedData; + + // >> observable-array-unshift-change + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { + //// Argument (args) is ChangedData. + //// args.eventName is "change". + //// args.action is "add". + //// args.index is 0. + //// args.removed.length is 0. + //// args.addedCount is equal to the number of inserted items. + + // >> (hide) + result = args; + // << (hide) + }); + + array.unshift(4, 5); + // << observable-array-unshift-change + + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 0 && result.addedCount === 2, "ObservableArray unshift() should raise 'change' event with correct args!"); +}; + +export const test_ObservableArray_indexOfShouldReturnCorrectIndex = function () { + // >> observable-array-indexof + const array = new ObservableArray(['one', 'two', 'three']); + const result = array.indexOf('two'); + // << observable-array-indexof + TKUnit.assert(result === 1, 'ObservableArray indexOf() should return correct index!'); +}; + +export const test_ObservableArray_indexOfShouldReturnCorrectIndexStartingFrom = function () { + // >> observable-array-indexof-args + const array = new ObservableArray(['one', 'two', 'three']); + const result = array.indexOf('two', 2); + // << observable-array-indexof-args + TKUnit.assert(result === -1, 'ObservableArray indexOf() should return correct index!'); +}; + +export const test_ObservableArray_lastIndexOfShouldReturnCorrectIndex = function () { + const array = new ObservableArray(['one', 'two', 'two', 'three']); + // >> observable-array-lastindexof + const result = array.lastIndexOf('two'); + // << observable-array-lastindexof + TKUnit.assert(result === 2, 'ObservableArray lastIndexOf() should return correct index!'); +}; + +export const test_ObservableArray_lastIndexOfShouldReturnCorrectIndexStartingFrom = function () { + // >> observable-array-lastindexof-args + const array = new ObservableArray(['one', 'two', 'two', 'one', 'three']); + const result = array.lastIndexOf('two', 1); + // << observable-array-lastindexof-args + TKUnit.assert(result === 1, 'ObservableArray lastIndexOf() should return correct index!'); +}; + +export const test_ObservableArray_settingLengthToZeroPerformsSplice = function () { + const array = new ObservableArray([1, 2, 3]); + + let changeRaised = false; + array.on('change', (args: ChangedData) => { + changeRaised = true; + TKUnit.assertEqual(args.object, array); + TKUnit.assertEqual(args.eventName, 'change'); + TKUnit.assertEqual(args.action, ChangeType.Splice); + TKUnit.assertEqual(args.index, 0); + TKUnit.assertEqual(args.addedCount, 0); + TKUnit.arrayAssert(args.removed, [1, 2, 3]); + }); + + array.length = 0; + + TKUnit.assertEqual(array.length, 0); + TKUnit.assertTrue(changeRaised); +}; + +export const test_ObservableArray_settingLengthToSomethingPerformsSplice = function () { + const array = new ObservableArray([1, 2, 3]); + let changeRaised = false; + + array.on('change', (args: ChangedData) => { + changeRaised = true; + TKUnit.assertEqual(args.object, array); + TKUnit.assertEqual(args.eventName, 'change'); + TKUnit.assertEqual(args.action, ChangeType.Splice); + TKUnit.assertEqual(args.index, 1); + TKUnit.assertEqual(args.addedCount, 0); + TKUnit.arrayAssert(args.removed, [2, 3]); + }); + + array.length = 1; + + TKUnit.assertEqual(array.length, 1); + TKUnit.assertTrue(changeRaised); +}; + +const array = new ObservableArray(); + +// We do not have indexer! +export const test_getItem_isDefined = function () { + TKUnit.assert(typeof array.getItem === 'function', "Method 'getItem()' should be defined!"); +}; + +export const test_setItem_isDefined = function () { + TKUnit.assert(typeof array.setItem === 'function', "Method 'setItem()' should be defined!"); +}; + +// Standard array properties and methods +export const test_length_isDefined = function () { + TKUnit.assert(typeof array.length === 'number', "Property 'length' should be defined!"); +}; + +export const test_toString_isDefined = function () { + TKUnit.assert(typeof array.toString === 'function', "Method 'toString()' should be defined!"); +}; + +export const test_toLocaleString_isDefined = function () { + TKUnit.assert(typeof array.toLocaleString === 'function', "Method 'toString()' should be defined!"); +}; + +export const test_concat_isDefined = function () { + TKUnit.assert(typeof array.concat === 'function', "Method 'concat()' should be defined!"); +}; + +export const test_join_isDefined = function () { + TKUnit.assert(typeof array.join === 'function', "Method 'join()' should be defined!"); +}; + +export const test_pop_isDefined = function () { + TKUnit.assert(typeof array.pop === 'function', "Method 'pop()' should be defined!"); +}; + +export const test_push_isDefined = function () { + TKUnit.assert(typeof array.push === 'function', "Method 'push()' should be defined!"); +}; + +export const test_reverse_isDefined = function () { + TKUnit.assert(typeof array.reverse === 'function', "Method 'reverse()' should be defined!"); +}; + +export const test_shift_isDefined = function () { + TKUnit.assert(typeof array.shift === 'function', "Method 'shift()' should be defined!"); +}; + +export const test_slice_isDefined = function () { + TKUnit.assert(typeof array.slice === 'function', "Method 'slice()' should be defined!"); +}; + +export const test_sort_isDefined = function () { + TKUnit.assert(typeof array.sort === 'function', "Method 'sort()' should be defined!"); +}; + +export const test_splice_isDefined = function () { + TKUnit.assert(typeof array.splice === 'function', "Method 'splice()' should be defined!"); +}; + +export const test_unshift_isDefined = function () { + TKUnit.assert(typeof array.unshift === 'function', "Method 'unshift()' should be defined!"); +}; + +export const test_indexOf_isDefined = function () { + TKUnit.assert(typeof array.indexOf === 'function', "Method 'indexOf()' should be defined!"); +}; + +export const test_lastIndexOf_isDefined = function () { + TKUnit.assert(typeof array.lastIndexOf === 'function', "Method 'lastIndexOf()' should be defined!"); +}; + +export const test_every_isDefined = function () { + TKUnit.assert(typeof array.every === 'function', "Method 'every()' should be defined!"); +}; + +export const test_some_isDefined = function () { + TKUnit.assert(typeof array.some === 'function', "Method 'some()' should be defined!"); +}; + +export const test_forEach_isDefined = function () { + TKUnit.assert(typeof array.forEach === 'function', "Method 'forEach()' should be defined!"); +}; + +export const test_map_isDefined = function () { + TKUnit.assert(typeof array.map === 'function', "Method 'map()' should be defined!"); +}; + +export const test_filter_isDefined = function () { + TKUnit.assert(typeof array.filter === 'function', "Method 'filter()' should be defined!"); +}; + +export const test_reduce_isDefined = function () { + TKUnit.assert(typeof array.reduce === 'function', "Method 'reduce()' should be defined!"); +}; + +export const test_reduce_without_initial_value = function () { + const sa = [1, 2, 3]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduce((a, b) => a + b); + TKUnit.assertEqual(result, 6, 'ObservableArray reduce function broken when initialValue is missing'); +}; + +export const test_reduce_with_initial_value = function () { + const sa = [1, 2, 3]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduce((a, b) => a + b, 5); + TKUnit.assertEqual(result, 11, 'ObservableArray reduce function broken when Initial Value is passed.'); +}; + +export const test_reduce_with_zero_as_initial_value = function () { + const sa = [{ prop: 1 }, { prop: 2 }, { prop: 3 }]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduce((a, b) => a + b.prop, 0); + TKUnit.assertEqual(result, 6, 'ObservableArray reduce function broken when Initial Value is zero.'); +}; + +export const test_reduceRight_isDefined = function () { + TKUnit.assert(typeof array.reduceRight === 'function', "Method 'reduceRight()' should be defined!"); +}; + +export const test_reduceRight_without_initial_value = function () { + const sa = [1, 2, 3]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduceRight((a, b) => a + b); + TKUnit.assertEqual(result, 6, 'ObservableArray reduceRight function broken when initialValue is missing'); +}; + +export const test_reduceRight_with_initial_value = function () { + const sa = [1, 2, 3]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduceRight((a, b) => a + b, 5); + TKUnit.assertEqual(result, 11, 'ObservableArray reduceRight function broken when Initial Value is passed.'); +}; + +export const test_reduceRight_with_zero_as_initial_value = function () { + const sa = [{ prop: 1 }, { prop: 2 }, { prop: 3 }]; + let array: ObservableArray = new ObservableArray(sa); + const result = array.reduceRight((a, b) => a + b.prop, 0); + TKUnit.assertEqual(result, 6, 'ObservableArray reduceRight function broken when Initial Value is zero.'); +}; diff --git a/packages/core/__tests__/e2e/automated/app/data/observable-array.md b/apps/automated/app/data/observable-array.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/data/observable-array.md rename to apps/automated/app/data/observable-array.md diff --git a/apps/automated/app/data/observable-tests.ts b/apps/automated/app/data/observable-tests.ts new file mode 100644 index 000000000..50fd2be07 --- /dev/null +++ b/apps/automated/app/data/observable-tests.ts @@ -0,0 +1,580 @@ +// >> observable-require +import { Observable, PropertyChangeData, EventData, WrappedValue, fromObject, fromObjectRecursive } from '@nativescript/core'; +// << observable-require + +import * as TKUnit from '../tk-unit'; +import * as types from '@nativescript/core/utils/types'; +import { ObservableArray } from '@nativescript/core'; + +var TESTED_NAME = 'tested'; +class TestObservable extends Observable { + public test() { + this._emit(TESTED_NAME); + } +} + +export var test_Observable_Constructor = function () { + // >> observable-creating + var json = { + Name: 'John', + Age: 34, + Married: true, + }; + var person = fromObject(json); + var name = person.get('Name'); + var age = person.get('Age'); + var married = person.get('Married'); + // console.log(name + " " + age + " " + married); // Prints out "John 34 true" if uncommented. + // << observable-creating + TKUnit.assert(name === 'John', 'Expected name is John'); + TKUnit.assert(age === 34, 'Expected age is 34'); + TKUnit.assert(married === true, 'Expected married is true'); +}; + +export var tests_DummyTestForCodeSnippet = function () { + // >> observable-property-change + var person = new Observable(); + person.set('Name', 'John'); + person.set('Age', 34); + person.set('Married', true); + person.addEventListener(Observable.propertyChangeEvent, function (pcd: PropertyChangeData) { + //console.log(pcd.eventName.toString() + " " + pcd.propertyName.toString() + " " + pcd.value.toString()); + }); + person.set('Age', 35); + person.set('Married', false); + // If uncommented, the console.log above produces the following output: + // propertyChange Age 35 + // propertyChange Married false + // << observable-property-change +}; + +export var test_Observable_Members = function () { + var obj = new Observable(); + TKUnit.assert(types.isDefined(obj.addEventListener), 'Observable.addEventListener not defined'); + TKUnit.assert(types.isDefined(obj._createPropertyChangeData), 'Observable.createPropertyChangeData not defined'); + TKUnit.assert(types.isDefined(obj._emit), 'Observable.emit not defined'); + TKUnit.assert(types.isDefined(obj.get), 'Observable.get not defined'); + TKUnit.assert(types.isDefined(obj.hasListeners), 'Observable.hasListeners not defined'); + TKUnit.assert(types.isDefined(obj.notify), 'Observable.notify not defined'); + TKUnit.assert(types.isDefined(obj.off), 'Observable.off not defined'); + TKUnit.assert(types.isDefined(obj.on), 'Observable.on not defined'); + TKUnit.assert(types.isDefined(obj.removeEventListener), 'Observable.removeEventListener not defined'); + TKUnit.assert(types.isDefined(obj.set), 'Observable.set not defined'); +}; + +export var test_Observable_UpdateAnotherPropertyWithinChangedCallback = function () { + var obj = new Observable(); + + var changedCallback = function (pcd: PropertyChangeData) { + if (pcd.propertyName === 'name') { + pcd.object.set('test', 'Changed test'); + } + }; + + obj.addEventListener(Observable.propertyChangeEvent, changedCallback); + + obj.set('name', 'Initial name'); + obj.set('test', 'Initial test'); + + TKUnit.assert(obj.get('name') === 'Initial name', 'Initial value for property name is not correct!'); + TKUnit.assert(obj.get('test') === 'Initial test', 'Initial value for property test is not correct!'); + + obj.set('name', 'Changed name'); + + TKUnit.assert(obj.get('name') === 'Changed name', 'Changed value for property name is not correct!'); + TKUnit.assert(obj.get('test') === 'Changed test', 'Changed value for property test is not correct!'); +}; + +// export var test_DependencyObservable_UpdateAnotherPropertyWithinChangedCallback = function () { +// var obj = new dependencyObservable.DependencyObservable(); + +// function onFirstPropertyChanged(data: dependencyObservable.PropertyChangeData) { +// var testObj = data.object; +// testObj._setValue(secondProperty, "Changed test"); +// }; + +// var firstProperty = new dependencyObservable.Property( +// "first", +// "obj", +// new proxy.PropertyMetadata( +// "", +// dependencyObservable.PropertyMetadataSettings.None, +// onFirstPropertyChanged +// ) +// ); + +// var secondProperty = new dependencyObservable.Property( +// "second", +// "obj", +// new proxy.PropertyMetadata( +// "", +// dependencyObservable.PropertyMetadataSettings.None, +// null +// ) +// ); + +// obj._setValue(firstProperty, "Initial name"); +// obj._setValue(secondProperty, "Initial test"); + +// TKUnit.assert(obj._getValue(firstProperty) === "Initial name", "Initial value for property name is not correct!"); +// TKUnit.assert(obj._getValue(secondProperty) === "Initial test", "Initial value for property test is not correct!"); + +// obj._setValue(firstProperty, "Changed name"); + +// TKUnit.assert(obj._getValue(firstProperty) === "Changed name", "Changed value for property name is not correct!"); +// TKUnit.assert(obj._getValue(secondProperty) === "Changed test", "Changed value for property test is not correct!"); +// } + +export var test_Observable_addEventListener_SingleEvent = function () { + var obj = new Observable(); + + var receivedCount = 0; + var callback = function (data: PropertyChangeData) { + receivedCount++; + TKUnit.assert(data.eventName === Observable.propertyChangeEvent, 'Expected event name ' + Observable.propertyChangeEvent); + TKUnit.assert(data.object === obj, 'PropertyChangeData.object value not valid.'); + TKUnit.assert(data.propertyName === 'testName', 'PropertyChangeData.propertyName value not valid.'); + TKUnit.assert(data.value === 1, 'PropertyChangeData.value value not valid.'); + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback); + obj.set('testName', 1); + TKUnit.assert(receivedCount === 1, 'PropertyChanged event not raised properly.'); +}; + +export var test_Observable_addEventListener_MultipleEvents = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback = function (data: EventData) { + receivedCount++; + TKUnit.assert(data.object === obj, 'EventData.object value not valid.'); + + if (data.eventName === Observable.propertyChangeEvent) { + var propertyData = data; + TKUnit.assert(propertyData.eventName === Observable.propertyChangeEvent, 'Expected event name ' + Observable.propertyChangeEvent); + TKUnit.assert(propertyData.propertyName === 'testName', 'PropertyChangeData.propertyName value not valid.'); + TKUnit.assert(propertyData.value === 1, 'PropertyChangeData.value value not valid.'); + } else { + TKUnit.assert(data.eventName === TESTED_NAME, 'Expected event name ' + TESTED_NAME); + } + }; + + var events = Observable.propertyChangeEvent + ',' + TESTED_NAME; + obj.addEventListener(events, callback); + obj.set('testName', 1); + obj.test(); + TKUnit.assert(receivedCount === 2, 'Callbacks not raised properly.'); +}; + +export var test_Observable_addEventListener_MultipleEvents_ShouldTrim = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback = function (data: EventData) { + receivedCount++; + }; + + var events = Observable.propertyChangeEvent + ' , ' + TESTED_NAME; + obj.addEventListener(events, callback); + TKUnit.assert(obj.hasListeners(Observable.propertyChangeEvent), 'Observable.addEventListener for multiple events should trim each event name.'); + TKUnit.assert(obj.hasListeners(TESTED_NAME), 'Observable.addEventListener for multiple events should trim each event name.'); + + obj.set('testName', 1); + obj.test(); + + TKUnit.assert(receivedCount === 2, 'Callbacks not raised properly.'); +}; + +export var test_Observable_addEventListener_MultipleCallbacks = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback1 = function (data: EventData) { + receivedCount++; + }; + + var callback2 = function (data: EventData) { + receivedCount++; + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback1); + obj.addEventListener(Observable.propertyChangeEvent, callback2); + + obj.set('testName', 1); + TKUnit.assert(receivedCount === 2, 'The propertyChanged notification should be raised twice.'); +}; + +export var test_Observable_addEventListener_MultipleCallbacks_MultipleEvents = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback1 = function (data: EventData) { + receivedCount++; + }; + + var callback2 = function (data: EventData) { + receivedCount++; + }; + + var events = Observable.propertyChangeEvent + ' , ' + TESTED_NAME; + obj.addEventListener(events, callback1); + obj.addEventListener(events, callback2); + + obj.set('testName', 1); + obj.test(); + + TKUnit.assert(receivedCount === 4, 'The propertyChanged notification should be raised twice.'); +}; + +export var test_Observable_removeEventListener_SingleEvent_SingleCallback = function () { + var obj = new Observable(); + + var receivedCount = 0; + var callback = function (data: EventData) { + receivedCount++; + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback); + obj.set('testName', 1); + + obj.removeEventListener(Observable.propertyChangeEvent, callback); + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), 'Observable.removeEventListener not working properly.'); + + obj.set('testName', 2); + TKUnit.assert(receivedCount === 1, 'Observable.removeEventListener not working properly.'); +}; + +export var test_Observable_removeEventListener_SingleEvent_MultipleCallbacks = function () { + var obj = new Observable(); + + var receivedCount = 0; + var callback1 = function (data: EventData) { + receivedCount++; + }; + + var callback2 = function (data: EventData) { + receivedCount++; + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback1); + obj.addEventListener(Observable.propertyChangeEvent, callback2); + obj.set('testName', 1); + + obj.removeEventListener(Observable.propertyChangeEvent, callback1); + TKUnit.assert(obj.hasListeners(Observable.propertyChangeEvent), 'Observable.removeEventListener not working properly with multiple listeners.'); + + obj.set('testName', 2); + TKUnit.assert(receivedCount === 3, 'Observable.removeEventListener not working properly with multiple listeners.'); + + obj.removeEventListener(Observable.propertyChangeEvent, callback2); + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), 'Observable.removeEventListener not working properly with multiple listeners.'); + + obj.set('testName', 3); + TKUnit.assert(receivedCount === 3, 'Observable.removeEventListener not working properly with multiple listeners.'); +}; + +export var test_Observable_removeEventListener_MutlipleEvents_SingleCallback = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback = function (data: EventData) { + receivedCount++; + }; + + var events = Observable.propertyChangeEvent + ' , ' + TESTED_NAME; + obj.addEventListener(events, callback); + + obj.set('testName', 1); + obj.test(); + + obj.removeEventListener(events, callback); + + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), 'Expected result for hasObservers is false'); + TKUnit.assert(!obj.hasListeners(TESTED_NAME), 'Expected result for hasObservers is false.'); + + obj.set('testName', 2); + obj.test(); + + TKUnit.assert(receivedCount === 2, 'Expected receive count is 2'); +}; + +export var test_Observable_removeEventListener_SingleEvent_NoCallbackSpecified = function () { + var obj = new TestObservable(); + + var receivedCount = 0; + var callback1 = function (data: EventData) { + receivedCount++; + }; + + var callback2 = function (data: EventData) { + receivedCount++; + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback1); + obj.addEventListener(Observable.propertyChangeEvent, callback2); + + obj.set('testName', 1); + obj.removeEventListener(Observable.propertyChangeEvent); + + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), 'Expected result for hasObservers is false.'); + + obj.set('testName', 2); + TKUnit.assert(receivedCount === 2, 'Expected receive count is 2'); +}; + +export var test_Observable_WhenCreatedWithJSON_PropertyChangedWithDotNotation_RaisesPropertyChangedEvent = function () { + var json = { + count: 5, + }; + var obj = fromObject(json); + + var receivedCount = 0; + var callback = function (data: PropertyChangeData) { + receivedCount++; + TKUnit.assert(data.eventName === Observable.propertyChangeEvent, 'Expected event name ' + Observable.propertyChangeEvent); + TKUnit.assert(data.object === obj, 'PropertyChangeData.object value not valid.'); + TKUnit.assert(data.propertyName === 'count', 'PropertyChangeData.propertyName value not valid.'); + TKUnit.assert(data.value === 6, 'PropertyChangeData.value value not valid.'); + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback); + + (obj).count++; + + TKUnit.assert(receivedCount === 1, 'PropertyChanged event not raised properly.'); +}; + +export var test_Observable_WhenCreatedWithJSON_PropertyChangedWithBracketsNotation_RaisesPropertyChangedEvent = function () { + var json = { + count: 5, + }; + var obj = fromObject(json); + + var receivedCount = 0; + var callback = function (data: PropertyChangeData) { + receivedCount++; + TKUnit.assert(data.eventName === Observable.propertyChangeEvent, 'Expected event name ' + Observable.propertyChangeEvent); + TKUnit.assert(data.object === obj, 'PropertyChangeData.object value not valid.'); + TKUnit.assert(data.propertyName === 'count', 'PropertyChangeData.propertyName value not valid.'); + TKUnit.assert(data.value === 6, 'PropertyChangeData.value value not valid.'); + }; + + obj.addEventListener(Observable.propertyChangeEvent, callback); + + obj['count']++; + + TKUnit.assert(receivedCount === 1, 'PropertyChanged event not raised properly.'); +}; + +export var test_AddingTwoEventHandlersAndRemovingWithinHandlerShouldRaiseAllEvents = function () { + var observableInstance = new Observable(); + var firstHandlerCalled = false; + var secondHandlerCalled = false; + + var firstHandler = function (args) { + observableInstance.off(Observable.propertyChangeEvent, firstHandler, firstObserver); + firstHandlerCalled = true; + }; + + var secondHandler = function (args) { + observableInstance.off(Observable.propertyChangeEvent, secondHandler, secondObserver); + secondHandlerCalled = true; + }; + + var firstObserver = new Observable(); + var secondObserver = new Observable(); + + observableInstance.on(Observable.propertyChangeEvent, firstHandler, firstObserver); + observableInstance.on(Observable.propertyChangeEvent, secondHandler, secondObserver); + + observableInstance.set('someProperty', 'some value'); + + TKUnit.assertEqual(firstHandlerCalled, true); + TKUnit.assertEqual(secondHandlerCalled, true); +}; + +export var test_ObservableCreatedWithJSON_shouldDistinguishSeparateObjects = function () { + var obj1 = { val: 1 }; + var obj2 = { val: 2 }; + var observable1 = fromObject(obj1); + var observable2 = fromObject(obj2); + + var val1 = observable1.get('val'); + var val2 = observable2.get('val'); + TKUnit.assert(val1 === 1 && val2 === 2, `Observable should keep separate objects separate! val1: ${val1}; val2: ${val2};`); + + var propName1; + var newValue1; + observable1.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => { + propName1 = data.propertyName; + newValue1 = data.value; + }); + + var propName2; + var newValue2; + observable2.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => { + propName2 = data.propertyName; + newValue2 = data.value; + }); + + observable1.set('val', 10); + TKUnit.assert(propName1 === 'val', "propName1 should be 'val'"); + TKUnit.assert(newValue1 === 10, 'newValue1 should be 10'); + + observable2.set('val', 20); + TKUnit.assert(propName2 === 'val', "propName2 should be 'val'"); + TKUnit.assert(newValue2 === 20, 'newValue2 should be 20'); + + val1 = observable1.get('val'); + val2 = observable2.get('val'); + TKUnit.assert(val1 === 10 && val2 === 20, `Observable should keep separate objects separate! val1: ${val1}; val2: ${val2};`); +}; + +export var test_ObservablesCreatedWithJSON_shouldNotInterfereWithOneAnother = function () { + var observable1 = fromObject({ property1: 1 }); + var observable2 = fromObject({ property2: 2 }); + + TKUnit.assert(observable1.get('property1') === 1, `Expected: 1; Actual: ${observable1.get('property1')}`); + TKUnit.assert(observable1.get('property2') === undefined, `Expected: undefined; Actual: ${observable1.get('property2')}`); + + TKUnit.assert(observable2.get('property1') === undefined, `Expected: undefined; Actual: ${observable2.get('property1')}`); + TKUnit.assert(observable2.get('property2') === 2, `Expected: 2; Actual: ${observable2.get('property2')}`); + + var propName1; + var newValue1; + observable1.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => { + propName1 = data.propertyName; + newValue1 = data.value; + }); + + var propName2; + var newValue2; + observable2.on(Observable.propertyChangeEvent, (data: PropertyChangeData) => { + propName2 = data.propertyName; + newValue2 = data.value; + }); + + observable1.set('property1', 10); + TKUnit.assert(propName1 === 'property1', "propName1 should be 'property1'"); + TKUnit.assert(newValue1 === 10, 'newValue1 should be 10'); + + observable2.set('property2', 20); + TKUnit.assert(propName2 === 'property2', "propName2 should be 'property2'"); + TKUnit.assert(newValue2 === 20, 'newValue2 should be 20'); +}; + +export function test_ObservablesCreatedWithJSON_shouldNotEmitTwoTimesPropertyChangeEvent() { + var testObservable = fromObject({ property1: 1 }); + var propertyChangeCounter = 0; + var propertyChangeHandler = function (args) { + propertyChangeCounter++; + }; + testObservable.on(Observable.propertyChangeEvent, propertyChangeHandler); + testObservable.set('property1', 2); + + TKUnit.assertEqual(propertyChangeCounter, 1, 'PropertyChange event should be fired only once for a single change.'); +} + +export function test_ObservableShouldEmitPropertyChangeWithSameObjectUsingWrappedValue() { + var testArray = [1]; + var testObservable = fromObject({ property1: testArray }); + var propertyChangeCounter = 0; + var propertyChangeHandler = function (args) { + propertyChangeCounter++; + }; + testObservable.on(Observable.propertyChangeEvent, propertyChangeHandler); + testArray.push(2); + + testObservable.set('property1', testArray); + + TKUnit.assertEqual(propertyChangeCounter, 0, 'PropertyChange event should not be fired when the same object instance is passed.'); + + testObservable.set('property1', WrappedValue.wrap(testArray)); + + TKUnit.assertEqual(propertyChangeCounter, 1, 'PropertyChange event should be fired only once for a single change.'); +} + +export function test_CorrectEventArgsWhenWrappedValueIsUsed() { + let testArray = [1]; + let testObservable = fromObject({ property1: testArray }); + let actualArgsValue; + let propertyChangeHandler = function (args) { + actualArgsValue = args.value; + }; + + testObservable.on(Observable.propertyChangeEvent, propertyChangeHandler); + testArray.push(2); + + let wrappedArray = WrappedValue.wrap(testArray); + + testObservable.set('property1', wrappedArray); + + TKUnit.assertEqual(actualArgsValue, testArray, 'PropertyChange event should be fired with correct value in arguments.'); +} + +export function test_CorrectPropertyValueAfterUsingWrappedValue() { + let testArray = [1]; + let testObservable = fromObject({ property1: testArray }); + + let wrappedArray = WrappedValue.wrap(testArray); + + testObservable.set('property1', wrappedArray); + + TKUnit.assertEqual(testObservable.get('property1'), testArray, 'WrappedValue is used only to execute property change logic and unwrapped value should be used as property value.'); +} + +export function test_CorrectPropertyValueAfterUsingStringEmptyWrappedValue() { + const emptyString = ''; + let testObservable = fromObject({ property1: emptyString }); + + let wrappedEmptyString = WrappedValue.wrap(emptyString); + + testObservable.set('property1', wrappedEmptyString); + + TKUnit.assertEqual(testObservable.get('property1'), emptyString, 'WrappedValue is used only to execute property change logic and unwrapped value should be used as property value.'); +} + +export function test_NestedObservablesWithObservableArrayShouldNotCrash() { + let someObservableArray = new ObservableArray(); + let testObservable = fromObjectRecursive({ + firstProp: 'test string', + secondProp: someObservableArray, + }); + TKUnit.assert(testObservable !== undefined); +} + +export function test_NestedObservableWithNullShouldNotCrash() { + let testObservable = fromObjectRecursive({ + someProperty: null, + }); + TKUnit.assert(testObservable !== undefined); +} + +export function test_get_set_on_observables_fromObject_without_property_in_json() { + const array = new ObservableArray(); + const vm = fromObject({}); + vm.set('p', array); + const value1 = vm.get('p'); + const value2 = (vm).p; + TKUnit.assertEqual(value1, array); + TKUnit.assertNull(value2); +} + +export function test_get_set_on_observables_fromObject_with_property_in_json() { + const array = new ObservableArray(); + const vm = fromObject({ p: null }); + vm.set('p', array); + const value1 = vm.get('p'); + const value2 = (vm).p; + TKUnit.assertEqual(value1, array); + TKUnit.assertEqual(value2, array); +} + +export function test_fromObjectRecursive_does_not_override_source_object_property() { + const myObj = {}; + const source = { name: 'a', value: myObj }; + const observable = fromObjectRecursive(source); + TKUnit.assertNotNull(observable); + TKUnit.assertEqual(source.value, myObj); +} diff --git a/packages/core/__tests__/e2e/automated/app/data/observable.md b/apps/automated/app/data/observable.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/data/observable.md rename to apps/automated/app/data/observable.md diff --git a/apps/automated/app/data/virtual-array-tests.ts b/apps/automated/app/data/virtual-array-tests.ts new file mode 100644 index 000000000..41934ff44 --- /dev/null +++ b/apps/automated/app/data/virtual-array-tests.ts @@ -0,0 +1,172 @@ +import * as TKUnit from '../tk-unit'; +import * as types from '@nativescript/core/utils/types'; +import { VirtualArray, ChangeType, ChangedData, ItemsLoading } from '@nativescript/core'; + +export var test_VirtualArray_shouldCreateArrayFromSpecifiedLength = function () { + var array = new VirtualArray(100); + + TKUnit.assert(array.length === 100, 'VirtualArray should create array from specified length!'); +}; + +export var test_VirtualArray_setItemShouldSetCorrectItem = function () { + var array = new VirtualArray(100); + array.setItem(0, 0); + TKUnit.assert(array.getItem(0) === 0, 'VirtualArray setItem() should set correct item!'); +}; + +export var test_VirtualArray_setItemShouldRaiseChangeEventWhenYouSetDifferentItem = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + var result: ChangedData; + var index = 0; + + array.on(VirtualArray.changeEvent, (args: ChangedData) => { + result = args; + }); + + array.setItem(index, 0); + + TKUnit.assert(result && result.eventName === 'change' && result.action === ChangeType.Update && result.removed.length === 1 && result.index === index && result.addedCount === 1, "VirtualArray setItem() should raise 'change' event with correct args!"); + + result = undefined; + + array.setItem(index, 1); + + TKUnit.assert(result && result.eventName === 'change' && result.action === ChangeType.Update && result.removed.length === 1 && result.index === index && result.addedCount === 1, "VirtualArray setItem() should raise 'change' event with correct args!"); + + array.on(VirtualArray.itemsLoadingEvent, (args: ItemsLoading) => { + // Argument (args) is ItemsLoading. + // args.index is start index of the page where the requested index is located. + // args.count number of requested items. + // + // Note: Virtual array will divide total number of items to pages using "loadSize" property value. When you request an + // item at specific index the array will raise "itemsLoading" event with "ItemsLoading" argument index set to the first index of the requested page + // and count set to number of items in this page. + // + // Important: If you have already loaded items in the requested page the array will raise multiple times "itemsLoading" event to request + // all ranges of still not loaded items in this page. + + var itemsToLoad = new Array(); + for (var i = 0; i < args.count; i++) { + itemsToLoad.push(i + args.index); + } + + array.load(args.index, itemsToLoad); + }); +}; + +export var test_VirtualArray_loadShouldRaiseChangeEventWithCorrectArgs = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + var result: ChangedData; + var index = 0; + + array.on(VirtualArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "update". + // args.removed.length and result.addedCount are equal to number of loaded items with load() method. + + result = args; + }); + + var itemsToLoad = [0, 1, 2]; + + array.load(index, itemsToLoad); + + TKUnit.assert(result && result.eventName === 'change' && result.action === ChangeType.Update && result.removed.length === itemsToLoad.length && result.index === index && result.addedCount === itemsToLoad.length, "VirtualArray load() should raise 'change' event with correct args!"); +}; + +export var test_VirtualArray_lengthIncreaseShouldRaiseChangeEventWithCorrectArgs = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + var result: ChangedData; + var index = array.length; + + array.on(VirtualArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "add". + // args.removed.length is 0, result.addedCount is equal to the delta between new and old "length" property values. + + result = args; + }); + + array.length += array.loadSize; + + TKUnit.assert(result && result.eventName === 'change' && result.action === ChangeType.Add && result.index === index && result.addedCount === array.loadSize && result.removed.length === 0, "VirtualArray length++ should raise 'change' event with correct args!"); +}; + +export var test_VirtualArray_lengthDecreaseShouldRaiseChangeEventWithCorrectArgs = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + var result: ChangedData; + var index = array.length; + + array.on(VirtualArray.changeEvent, (args: ChangedData) => { + // Argument (args) is ChangedData. + // args.eventName is "change". + // args.action is "remove". + // result.addedCount is 0, args.removed.length is equal to the delta between new and old "length" property values. + + result = args; + }); + + array.length -= array.loadSize; + + TKUnit.assert(result && result.eventName === 'change' && result.action === ChangeType.Delete && result.index === index && result.removed.length === array.loadSize && result.addedCount === 0, "VirtualArray length++ should raise 'change' event with correct args!"); +}; + +export var test_VirtualArray_shouldRaiseItemsLoadingIfIndexIsNotLoadedAndGetItemIsCalled = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + var result: ItemsLoading; + + array.on(VirtualArray.itemsLoadingEvent, (args: ItemsLoading) => { + result = args; + }); + + array.getItem(0); + + TKUnit.assert(result.eventName === VirtualArray.itemsLoadingEvent && result.index === 0 && result.count === array.loadSize, "VirtualArray getItem() should raise 'itemsLoading' event with correct args if item is not loaded!"); +}; + +export var test_VirtualArray_shouldNotRaiseItemsLoadingIfIndexIsLoadedAndGetItemIsCalled = function () { + var array = new VirtualArray(100); + array.setItem(0, 0); + array.loadSize = 15; + + var result: ItemsLoading; + + array.on(VirtualArray.itemsLoadingEvent, (args: ItemsLoading) => { + result = args; + }); + + array.getItem(0); + + TKUnit.assert(types.isUndefined(result), "VirtualArray getItem() should not raise 'itemsLoading' event if item is loaded!"); +}; + +export var test_VirtualArray_shouldRaiseItemsLoadingIfIndexIsNotLoadedAndGetItemIsCalledCorrectNumberOfTimesWithCorrectArgs = function () { + var array = new VirtualArray(100); + array.loadSize = 15; + + array.setItem(0, 0); + + array.setItem(5, 5); + + var result = new Array(); + + array.on(VirtualArray.itemsLoadingEvent, (args: ItemsLoading) => { + result.push(args); + }); + + array.getItem(1); + + TKUnit.assert(result.length === 2 && result[0].eventName === VirtualArray.itemsLoadingEvent && result[0].index === 1 && result[0].count === 4 && result[1].eventName === VirtualArray.itemsLoadingEvent && result[1].index === 6 && result[1].count === 9, "VirtualArray getItem() should raise 'itemsLoading' event with correct args!"); +}; diff --git a/packages/core/__tests__/e2e/automated/app/data/virtual-array.md b/apps/automated/app/data/virtual-array.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/data/virtual-array.md rename to apps/automated/app/data/virtual-array.md diff --git a/apps/automated/app/debugger/dom-node-tests.ts b/apps/automated/app/debugger/dom-node-tests.ts new file mode 100644 index 000000000..52b8b8ad8 --- /dev/null +++ b/apps/automated/app/debugger/dom-node-tests.ts @@ -0,0 +1,395 @@ +import { assert, assertEqual } from '../tk-unit'; +import { DOMNode } from '@nativescript/core/debugger/dom-node'; +import { attachDOMInspectorCommandCallbacks, attachCSSInspectorCommandCallbacks, attachDOMInspectorEventCallbacks } from '@nativescript/core/debugger/devtools-elements'; +import { InspectorCommands, InspectorEvents } from '@nativescript/core/debugger/devtools-elements'; +import { unsetValue } from '@nativescript/core/ui/core/properties'; +import { Button } from '@nativescript/core/ui/button'; +import { Slider } from '@nativescript/core/ui/slider'; +import { Label } from '@nativescript/core/ui/label'; +import { textProperty } from '@nativescript/core/ui/text-base'; +import { TextView } from '@nativescript/core/ui/text-view'; +import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; +import { isAndroid } from '@nativescript/core/platform'; + +let originalInspectorGlobal: InspectorCommands & InspectorEvents; + +let currentInspector: InspectorCommands & InspectorEvents; +function getTestInspector(): InspectorCommands & InspectorEvents { + let inspector = { + getDocument(): any { + return {}; + }, + removeNode(nodeId: number): void { + /* */ + }, + getComputedStylesForNode(nodeId: number): any { + return []; + }, + setAttributeAsText(nodeId: number, text: string, name: string): void { + /* */ + }, + + childNodeInserted(parentId: number, lastId: number, node: string | DOMNode): void { + /* to be replaced */ + }, + childNodeRemoved(parentId: number, nodeId: number): void { + /* to be replaced */ + }, + attributeModified(nodeId: number, attrName: string, attrValue: string) { + /* to be replaced */ + }, + attributeRemoved(nodeId: number, attrName: string) { + /* to be replaced */ + }, + }; + + attachDOMInspectorCommandCallbacks(inspector); + attachDOMInspectorEventCallbacks(inspector); + attachCSSInspectorCommandCallbacks(inspector); + + return inspector; +} + +function getIOSDOMInspector() { + return { + events: { + childNodeInserted(parentId: number, lastId: number, node: DOMNode): void { + return; + }, + childNodeRemoved(parentId: number, nodeId: number): void { + return; + }, + attributeModified(nodeId: number, attrName: string, attrValue: string): void { + return; + }, + attributeRemoved(nodeId: number, attrName: string): void { + return; + }, + }, + commands: { + getDocument(): any { + return {}; + }, + removeNode(nodeId: number): void { + /* */ + }, + getComputedStylesForNode(nodeId: number): any { + return []; + }, + setAttributeAsText(nodeId: number, text: string, name: string): void { + /* */ + }, + }, + }; +} + +export function setUp(): void { + if (isAndroid) { + originalInspectorGlobal = global.__inspector; + currentInspector = getTestInspector(); + global.__inspector = currentInspector; + } else { + let domInspector = getIOSDOMInspector(); + currentInspector = getTestInspector(); + domInspector.events = currentInspector; + } +} + +export function tearDown(): void { + if (isAndroid) { + global.__inspector = originalInspectorGlobal; + } +} + +function assertAttribute(domNode: DOMNode, name: string, value: any) { + const propIdx = domNode.attributes.indexOf(name); + assert(propIdx >= 0, `Attribute ${name} not found`); + assertEqual(domNode.attributes[propIdx + 1], value); +} + +export function test_custom_attribute_is_reported_in_dom_node() { + const btn = new Button(); + btn['test_prop'] = 'test_value'; + btn.ensureDomNode(); + const domNode = btn.domNode; + assertAttribute(domNode, 'test_prop', 'test_value'); +} + +export function test_custom__falsy_attribute_is_reported_in_dom_node() { + const btn = new Button(); + btn['test_prop_null'] = null; + btn['test_prop_0'] = 0; + btn['test_prop_undefined'] = undefined; + btn['test_prop_empty_string'] = ''; + + btn.ensureDomNode(); + const domNode = btn.domNode; + assertAttribute(domNode, 'test_prop_null', null + ''); + assertAttribute(domNode, 'test_prop_0', 0 + ''); + assertAttribute(domNode, 'test_prop_undefined', undefined + ''); + assertAttribute(domNode, 'test_prop_empty_string', ''); +} + +export function test_property_is_reported_in_dom_node() { + const btn = new Button(); + btn.text = 'test_value'; + btn.ensureDomNode(); + const domNode = btn.domNode; + assertAttribute(domNode, 'text', 'test_value'); +} + +export function test_childNodeInserted_in_dom_node() { + let childNodeInsertedCalled = false; + let actualParentId = 0; + let expectedParentId = 0; + + currentInspector.childNodeInserted = (parentId, lastNodeId, node) => { + childNodeInsertedCalled = true; + actualParentId = parentId; + }; + + const stack = new StackLayout(); + stack.ensureDomNode(); + expectedParentId = stack._domId; + + const btn1 = new Button(); + btn1.text = 'button1'; + stack.addChild(btn1); + + assert(childNodeInsertedCalled, 'global inspector childNodeInserted not called.'); + assertEqual(actualParentId, expectedParentId); +} + +export function test_childNodeInserted_at_index_in_dom_node() { + const stack = new StackLayout(); + stack.ensureDomNode(); + + // child index 0 + const btn1 = new Button(); + btn1.text = 'button1'; + stack.addChild(btn1); + + // child index 1 + const btn2 = new Button(); + btn2.text = 'button2'; + stack.addChild(btn2); + + // child index 2 + const btn3 = new Button(); + btn3.text = 'button3'; + stack.addChild(btn3); + + const lbl = new Label(); + lbl.text = 'label me this'; + + let called = false; + currentInspector.childNodeInserted = (parentId, lastNodeId, node: any) => { + assertEqual(lastNodeId, btn1._domId, "Child inserted at index 1's previous sibling does not match."); + assertEqual(node.toObject().nodeId, lbl._domId, "Child id doesn't match"); + called = true; + }; + + stack.insertChild(lbl, 1); + assert(called, 'childNodeInserted not called'); +} + +export function test_childNodeRemoved_in_dom_node() { + let childNodeRemovedCalled = false; + let actualRemovedNodeId = 0; + let expectedRemovedNodeId = 0; + + currentInspector.childNodeRemoved = (parentId, nodeId) => { + childNodeRemovedCalled = true; + actualRemovedNodeId = nodeId; + }; + + const stack = new StackLayout(); + stack.ensureDomNode(); + + const btn1 = new Button(); + btn1.text = 'button1'; + expectedRemovedNodeId = btn1._domId; + stack.addChild(btn1); + + const btn2 = new Button(); + btn2.text = 'button2'; + stack.addChild(btn2); + + stack.removeChild(btn1); + console.log('btn2: ' + btn2); + + assert(childNodeRemovedCalled, 'global inspector childNodeRemoved not called.'); + assertEqual(actualRemovedNodeId, expectedRemovedNodeId); +} + +export function test_falsy_property_is_reported_in_dom_node() { + const btn = new Button(); + btn.text = null; + btn.ensureDomNode(); + const domNode = btn.domNode; + assertAttribute(domNode, 'text', 'null'); + + btn.text = undefined; + domNode.loadAttributes(); + assertAttribute(domNode, 'text', 'undefined'); +} + +export function test_property_change_calls_attributeModified() { + const btn = new Button(); + btn.ensureDomNode(); + const domNode = btn.domNode; + + let callbackCalled = false; + currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => { + assertEqual(nodeId, domNode.nodeId, 'nodeId'); + assertEqual(attrName, 'text', 'attrName'); + assertEqual(attrValue, 'new value', 'attrValue'); + callbackCalled = true; + }; + + btn.text = 'new value'; + + assert(callbackCalled, 'attributeModified not called'); +} + +export function test_property_change_from_native_calls_attributeModified() { + const tv = new TextView(); + tv.ensureDomNode(); + const domNode = tv.domNode; + + let callbackCalled = false; + currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => { + assertEqual(nodeId, domNode.nodeId, 'nodeId'); + assertEqual(attrName, 'text', 'attrName'); + assertEqual(attrValue, 'new value', 'attrValue'); + callbackCalled = true; + }; + + textProperty.nativeValueChange(tv, 'new value'); + + assert(callbackCalled, 'attributeModified not called'); +} + +export function test_property_reset_calls_attributeRemoved() { + const btn = new Button(); + btn.text = 'some value'; + btn.ensureDomNode(); + const domNode = btn.domNode; + + let callbackCalled = false; + currentInspector.attributeRemoved = (nodeId: number, attrName: string) => { + assertEqual(nodeId, domNode.nodeId, 'nodeId'); + assertEqual(attrName, 'text', 'attrName'); + callbackCalled = true; + }; + + btn.text = unsetValue; + + assert(callbackCalled, 'attributeRemoved not called'); +} + +export function test_coercible_property_change_calls_attributeModified() { + const slider = new Slider(); + slider.ensureDomNode(); + const domNode = slider.domNode; + + let callbackCalled = false; + currentInspector.attributeModified = (nodeId: number, attrName: string, attrValue: string) => { + assertEqual(nodeId, domNode.nodeId, 'nodeId'); + assertEqual(attrName, 'value', 'attrName'); + assertEqual(attrValue, '10', 'attrValue'); + callbackCalled = true; + }; + + slider.value = 10; + + assert(callbackCalled, 'attributeModified not called'); +} + +export function test_coercible_property_reset_calls_attributeRemoved() { + const slider = new Slider(); + slider.value = 10; + slider.ensureDomNode(); + const domNode = slider.domNode; + + let callbackCalled = false; + currentInspector.attributeRemoved = (nodeId: number, attrName: string) => { + assertEqual(nodeId, domNode.nodeId, 'nodeId'); + assertEqual(attrName, 'value', 'attrName'); + callbackCalled = true; + }; + + slider.value = unsetValue; + + assert(callbackCalled, 'attributeRemoved not called'); +} + +export function test_inspector_ui_setAttributeAsText_set_existing_property() { + // Arrange + const label = new Label(); + + label.text = 'original label'; + const expectedValue = 'updated label'; + + label.ensureDomNode(); + + // Act + // simulate call from the inspector UI + currentInspector.setAttributeAsText(label.domNode.nodeId, "text='" + expectedValue + "'", 'text'); + + // Assert + assertEqual(label.text, expectedValue); +} + +export function test_inspector_ui_setAttributeAsText_remove_existing_property() { + // Arrange + const label = new Label(); + label.text = 'original label'; + + label.ensureDomNode(); + + // Act + // simulate call from the inspector UI + currentInspector.setAttributeAsText(label.domNode.nodeId, '' /* empty value - removes the attribute */, 'text'); + + // Assert + assertEqual(label.text, ''); +} + +export function test_inspector_ui_setAttributeAsText_set_new_property() { + // Arrange + const label = new Label(); + const expectedValue = 'custom'; + + label.ensureDomNode(); + + // Act + // simulate call from the inspector UI + currentInspector.setAttributeAsText(label.domNode.nodeId, "data-attr='" + expectedValue + "'" /* data-attr="custom" */, ' ' /* empty attr name initially */); + + // Assert + assertEqual(label['data-attr'], expectedValue); +} + +export function test_inspector_ui_removeNode() { + let childNodeRemovedCalled = false; + let stack = new StackLayout(); + let label = new Label(); + stack.addChild(label); + + stack.ensureDomNode(); + label.ensureDomNode(); + + let expectedParentId = stack.domNode.nodeId; + let expectedNodeId = label.domNode.nodeId; + + currentInspector.childNodeRemoved = (parentId, nodeId) => { + childNodeRemovedCalled = true; + assertEqual(parentId, expectedParentId); + assertEqual(nodeId, expectedNodeId); + }; + + currentInspector.removeNode(label.domNode.nodeId); + + assert(childNodeRemovedCalled, 'childNodeRemoved callback not called.'); +} diff --git a/apps/automated/app/fetch/fetch-tests.ts b/apps/automated/app/fetch/fetch-tests.ts new file mode 100644 index 000000000..cf7d8cb44 --- /dev/null +++ b/apps/automated/app/fetch/fetch-tests.ts @@ -0,0 +1,204 @@ +/* tslint:disable:no-unused-variable */ +import * as TKUnit from '../tk-unit'; +import * as types from '@nativescript/core/utils/types'; + +export var test_fetch_defined = function () { + TKUnit.assert(types.isDefined(fetch), 'Method fetch() should be defined!'); +}; + +export var test_fetch = function (done: (err: Error, res?: string) => void) { + // >> fetch-response + fetch('https://httpbin.org/get') + .then(function (r) { + // Argument (r) is Response! + // >> (hide) + TKUnit.assert(r instanceof Response, 'Result from fetch() should be valid Response object! Actual result is: ' + r); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-response +}; + +export var test_fetch_text = function (done: (err: Error, res?: string) => void) { + // >> fetch-string + fetch('https://httpbin.org/get') + .then((response) => response.text()) + .then(function (r) { + // Argument (r) is string! + // >> (hide) + TKUnit.assert(types.isString(r), 'Result from text() should be string! Actual result is: ' + r); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-string +}; + +export var test_fetch_json = function (done: (err: Error, res?: string) => void) { + // >> fetch-json + fetch('https://httpbin.org/get') + .then((response) => response.json()) + .then(function (r) { + // Argument (r) is JSON object! + // >> (hide) + TKUnit.assertNotNull(r, 'Result from json() should be JSON object!'); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-json +}; + +export var test_fetch_formData = function (done: (err: Error, res?: string) => void) { + // >> fetch-formdata + fetch('https://httpbin.org/get') + .then((response) => response.formData()) + .then(function (r) { + // Argument (r) is FormData object! + // >> (hide) + TKUnit.assert(r instanceof FormData, 'Result from formData() should be FormData object! Actual result is: ' + r); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-formdata +}; + +export var test_fetch_blob = function (done: (err: Error, res?: string) => void) { + // >> fetch-blob + fetch('https://httpbin.org/get') + .then((response) => response.blob()) + .then(function (r) { + // Argument (r) is Blob object! + // >> (hide) + TKUnit.assertNotNull(r, 'Result from blob() should be Blob object!'); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-blob +}; + +export var test_fetch_arraybuffer = function (done: (err: Error, res?: string) => void) { + // >> fetch-arraybuffer + fetch('https://httpbin.org/get') + .then((response) => response.arrayBuffer()) + .then(function (r) { + // Argument (r) is ArrayBuffer object! + // >> (hide) + TKUnit.assertNotNull(r, 'Result from arrayBuffer() should be ArrayBuffer object!'); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-arraybuffer +}; + +export var test_fetch_fail_invalid_url = function (done) { + var completed: boolean; + var isReady = function () { + return completed; + }; + + fetch('hgfttp://httpbin.org/get') + .catch(function (e) { + completed = true; + done(null); + }) + .catch(failOnError(done)); +}; + +// Note: fetch is unable to do url validation +// export var test_fetch_invalid_url_fail_message = function (done) { +// fetch("hgfttp://httpbin.org/get").catch(function (e: TypeError) { +// TKUnit.assert(e.message.match(/Network request failed:.{2,}/), "Failure message should contain details on the failure. Actual message was: " + e.message); +// done(null); +// }).catch(failOnError(done)); +// }; + +export var test_fetch_response_status = function (done) { + // >> fetch-status-response + fetch('https://httpbin.org/get') + .then(function (response) { + // Argument (response) is Response! + var statusCode = response.status; + // >> (hide) + TKUnit.assert(types.isDefined(statusCode), 'response.status should be defined! Actual result is: ' + statusCode); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-status-response +}; + +export var test_fetch_response_headers = function (done) { + // >> fetch-headers-response + fetch('https://httpbin.org/get') + .then(function (response) { + // Argument (response) is Response! + // var all = response.headers.getAll(); + // >> (hide) + TKUnit.assert(types.isDefined(response.headers), 'response.headers should be defined! Actual result is: ' + response.headers); + done(null); + // << (hide) + }) + .catch(failOnError(done)); + // << fetch-headers-response +}; + +export var test_fetch_headers_sent = function (done) { + fetch('https://httpbin.org/get', { + method: 'GET', + headers: new Headers({ 'Content-Type': 'application/json' }), + }) + .then(function (response) { + var result = response.headers; + TKUnit.assert(result.get('Content-Type') === 'application/json', 'Headers not sent/received properly! Actual result is: ' + result); + done(null); + }) + .catch(failOnError(done)); +}; + +export var test_fetch_post_form_data = function (done) { + var data = new FormData(); + data.append('MyVariableOne', 'ValueOne'); + data.append('MyVariableTwo', 'ValueTwo'); + + fetch('https://httpbin.org/post', { + method: 'POST', + headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }), + body: data, + }) + .then((r) => { + return r.formData(); + }) + .then(function (r) { + TKUnit.assert(r instanceof FormData, 'Content not sent/received properly! Actual result is: ' + r); + done(null); + }) + .catch(failOnError(done)); +}; + +export var test_fetch_post_json = function (done) { + // >> fetch-post-json + fetch('https://httpbin.org/post', { + method: 'POST', + headers: new Headers({ 'Content-Type': 'application/json' }), + body: JSON.stringify({ MyVariableOne: 'ValueOne', MyVariableTwo: 'ValueTwo' }), + }) + .then((r) => r.json()) + .then(function (r) { + // >> (hide) + TKUnit.assert(r.json['MyVariableOne'] === 'ValueOne' && r.json['MyVariableTwo'] === 'ValueTwo', 'Content not sent/received properly! Actual result is: ' + r.json); + done(null); + // << (hide) + // console.log(result); + }) + .catch(failOnError(done)); + // << fetch-post-json +}; + +const failOnError = function (done: (err: Error, res?: string) => void) { + return (e) => done(e); +}; diff --git a/packages/core/__tests__/e2e/automated/app/fetch/fetch.md b/apps/automated/app/fetch/fetch.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fetch/fetch.md rename to apps/automated/app/fetch/fetch.md diff --git a/apps/automated/app/file-system-access-tests/file-system-access-tests.ts b/apps/automated/app/file-system-access-tests/file-system-access-tests.ts new file mode 100644 index 000000000..03f02c366 --- /dev/null +++ b/apps/automated/app/file-system-access-tests/file-system-access-tests.ts @@ -0,0 +1,49 @@ +import * as TKUnit from '../tk-unit'; +import { knownFolders, path, File, Folder } from '@nativescript/core'; + +export var test_UTF8_BOM_is_not_returned = function () { + const folder1 = path.join(knownFolders.documents().path, 'file-system-access-tests'); + if (!Folder.exists(folder1)) { + Folder.fromPath(folder1); + } + var filePath = path.join(folder1, 'xml.expected'); + var file = File.fromPath(filePath); + + var onError = function (error) { + TKUnit.assert(false, 'Could not read file xml.expected'); + }; + + var text = file.readTextSync(onError); + if (text) { + var actualCharCode = text.charCodeAt(0); + var expectedCharCode = '{'.charCodeAt(0); + TKUnit.assert(actualCharCode === expectedCharCode, 'Actual character code: ' + actualCharCode + '; Expected character code: ' + expectedCharCode); + } +}; + +export var test_file_exists_on_folder = function () { + const folder1 = path.join(knownFolders.documents().path, 'file-system-access-tests'); + if (!Folder.exists(folder1)) { + Folder.fromPath(folder1); + } + var filePath = path.join(folder1, 'folder'); + if (!Folder.exists(filePath)) { + Folder.fromPath(filePath); + } + + if (!Folder.exists(filePath)) { + TKUnit.assert(false, `Could not read path ${filePath}`); + + return; + } + + TKUnit.assertTrue(File.exists(filePath), 'File.exists() returned false for folder!'); +}; + +export var test_leading_slash_is_not_returned = function () { + var parts = ['app', 'tns_modules', 'fileName']; + var expected = parts.join('/'); + var filePath = path.join(...parts); + + TKUnit.assertEqual(filePath, expected, 'Leading slash should not be part of the path'); +}; diff --git a/packages/core/__tests__/e2e/automated/app/file-system-access-tests/folder/file.expected b/apps/automated/app/file-system-access-tests/folder/file.expected similarity index 100% rename from packages/core/__tests__/e2e/automated/app/file-system-access-tests/folder/file.expected rename to apps/automated/app/file-system-access-tests/folder/file.expected diff --git a/packages/core/__tests__/e2e/automated/app/file-system-access-tests/xml.expected b/apps/automated/app/file-system-access-tests/xml.expected similarity index 100% rename from packages/core/__tests__/e2e/automated/app/file-system-access-tests/xml.expected rename to apps/automated/app/file-system-access-tests/xml.expected diff --git a/apps/automated/app/file-system/file-system-tests.ts b/apps/automated/app/file-system/file-system-tests.ts new file mode 100644 index 000000000..7913fc820 --- /dev/null +++ b/apps/automated/app/file-system/file-system-tests.ts @@ -0,0 +1,700 @@ +/* tslint:disable:no-unused-variable */ +// >> file-system-require +import * as fs from '@nativescript/core/file-system'; +// << file-system-require + +import * as TKUnit from '../tk-unit'; +import * as appModule from '@nativescript/core/application'; +import { isIOS, Device, platformNames } from '@nativescript/core'; + +export var testPathNormalize = function () { + // >> file-system-normalize + var documents = fs.knownFolders.documents(); + var testPath = '///test.txt'; + // Get a normalized path such as /test.txt from ///test.txt + var normalizedPath = fs.path.normalize(documents.path + testPath); + // >> (hide) + var expected = documents.path + '/test.txt'; + TKUnit.assert(normalizedPath === expected); + // << (hide) + // << file-system-normalize +}; + +export var testPathJoin = function () { + // >> file-system-multiple-args + var documents = fs.knownFolders.documents(); + // Generate a path like /myFiles/test.txt + var path = fs.path.join(documents.path, 'myFiles', 'test.txt'); + // >> (hide) + var expected = documents.path + '/myFiles/test.txt'; + TKUnit.assert(path === expected); + // << (hide) + // << file-system-multiple-args +}; + +export var testPathSeparator = function () { + // >> file-system-separator + // An OS dependent path separator, "\" or "/". + var separator = fs.path.separator; + // >> (hide) + var expected = '/'; + TKUnit.assert(separator === expected); + // << (hide) + // << file-system-separator +}; + +export var testFileFromPath = function () { + // >> file-system-create + var documents = fs.knownFolders.documents(); + var path = fs.path.join(documents.path, 'FileFromPath.txt'); + var file = fs.File.fromPath(path); + + // Writing text to the file. + file.writeText('Something').then( + function () { + // Succeeded writing to the file. + // >> (hide) + file.readText().then( + function (content) { + TKUnit.assert(content === 'Something', 'File read/write not working.'); + file.remove(); + }, + function (error) { + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + } + ); + // << (hide) + }, + function (error) { + // Failed to write to the file. + // >> (hide) + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + // << (hide) + } + ); + // << file-system-create +}; + +export var testFolderFromPath = function () { + // >> file-system-create-folder + var path = fs.path.join(fs.knownFolders.documents().path, 'music'); + var folder = fs.Folder.fromPath(path); + // >> (hide) + TKUnit.assert(folder, 'Folder.getFolder API not working.'); + TKUnit.assert(fs.Folder.exists(folder.path), 'Folder.getFolder API not working.'); + folder.remove(); + // << (hide) + // << file-system-create-folder +}; + +export var testFileWrite = function () { + // >> file-system-write-string + var documents = fs.knownFolders.documents(); + var file = documents.getFile('Test_Write.txt'); + + // Writing text to the file. + file.writeText('Something').then( + function () { + // Succeeded writing to the file. + // >> (hide) + file.readText().then( + function (content) { + TKUnit.assert(content === 'Something', 'File read/write not working.'); + file.remove(); + }, + function (error) { + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + } + ); + // << (hide) + }, + function (error) { + // Failed to write to the file. + // >> (hide) + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + // << (hide) + } + ); + // << file-system-write-string +}; + +export var testGetFile = function () { + // >> file-system-create-file + var documents = fs.knownFolders.documents(); + var file = documents.getFile('NewFileToCreate.txt'); + // >> (hide) + TKUnit.assert(file, 'File.getFile API not working.'); + TKUnit.assert(fs.File.exists(file.path), 'File.getFile API not working.'); + file.remove(); + // << (hide) + // << file-system-create-file +}; + +export var testGetFolder = function () { + // >> file-system-get-folder + var documents = fs.knownFolders.documents(); + var folder = documents.getFolder('NewFolderToCreate'); + // >> (hide) + TKUnit.assert(folder, 'Folder.getFolder API not working.'); + TKUnit.assert(fs.Folder.exists(folder.path), 'Folder.getFolder API not working.'); + folder.remove(); + // << (hide) + // << file-system-get-folder +}; + +export var testFileRead = function () { + // >> file-system-example-text + var documents = fs.knownFolders.documents(); + var myFile = documents.getFile('Test_Write.txt'); + + var written: boolean; + // Writing text to the file. + myFile.writeText('Something').then( + function () { + // Succeeded writing to the file. + + // Getting back the contents of the file. + myFile.readText().then( + function (content) { + // Successfully read the file's content. + // >> (hide) + written = content === 'Something'; + TKUnit.assert(written, 'File read/write not working.'); + myFile.remove(); + // << (hide) + }, + function (error) { + // Failed to read from the file. + // >> (hide) + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + // << (hide) + } + ); + }, + function (error) { + // Failed to write to the file. + // >> (hide) + TKUnit.assert(false, 'Failed to read/write text'); + //console.dir(error); + // << (hide) + } + ); + // << file-system-example-text +}; + +export var testFileReadWriteBinary = function () { + // >> file-system-read-binary + var fileName = 'logo.png'; + var error; + + var sourceFile = fs.File.fromPath(__dirname + '/assets/' + fileName); + var destinationFile = fs.knownFolders.documents().getFile(fileName); + + var source = sourceFile.readSync((e) => { + error = e; + }); + + destinationFile.writeSync(source, (e) => { + error = e; + }); + + // >> (hide) + var destination = destinationFile.readSync((e) => { + error = e; + }); + TKUnit.assertNull(error); + if (Device.os === platformNames.ios) { + TKUnit.assertTrue(source.isEqualToData(destination)); + } else { + TKUnit.assertEqual(new java.io.File(sourceFile.path).length(), new java.io.File(destinationFile.path).length()); + } + + destinationFile.removeSync(); + // << (hide) + // << file-system-read-binary +}; + +export var testFileReadWriteBinaryAsync = function () { + // >> file-system-read-binary-async + var fileName = 'logo.png'; + + var sourceFile = fs.File.fromPath(__dirname + '/assets/' + fileName); + var destinationFile = fs.knownFolders.documents().getFile(fileName); + + // Read the file + sourceFile.read().then( + function (source) { + // Succeeded in reading the file + // >> (hide) + destinationFile.write(source).then( + function () { + // Succeded in writing the file + destinationFile.read().then( + function (destination) { + if (Device.os === platformNames.ios) { + TKUnit.assertTrue(source.isEqualToData(destination)); + } else { + TKUnit.assertEqual(new java.io.File(sourceFile.path).length(), new java.io.File(destinationFile.path).length()); + } + + destinationFile.removeSync(); + }, + function (error) { + TKUnit.assert(false, 'Failed to read destination binary async'); + } + ); + }, + function (error) { + // Failed to write the file. + TKUnit.assert(false, 'Failed to write binary async'); + } + ); + // << (hide) + }, + function (error) { + // Failed to read the file. + // >> (hide) + TKUnit.assert(false, 'Failed to read binary async'); + // << (hide) + } + ); + // << file-system-read-binary-async +}; + +export var testGetKnownFolders = function () { + // >> file-system-known-folders + // Getting the application's 'documents' folder. + var documents = fs.knownFolders.documents(); + // >> (hide) + TKUnit.assert(documents, 'Could not retrieve the Documents known folder.'); + TKUnit.assert(documents.isKnown, 'The Documents folder should have its isKnown property set to true.'); + // << (hide) + // Getting the application's 'temp' folder. + var temp = fs.knownFolders.temp(); + // >> (hide) + TKUnit.assert(temp, 'Could not retrieve the Temporary known folder.'); + TKUnit.assert(temp.isKnown, 'The Temporary folder should have its isKnown property set to true.'); + // << (hide) + // << file-system-known-folders +}; + +function _testIOSSpecificKnownFolder(knownFolderName: string) { + let knownFolder: fs.Folder; + let createdFile: fs.File; + let testFunc = function testFunc() { + knownFolder = fs.knownFolders.ios[knownFolderName](); + if (knownFolder) { + createdFile = knownFolder.getFile('createdFile'); + createdFile.writeTextSync('some text'); + } + }; + if (isIOS) { + testFunc(); + if (knownFolder) { + TKUnit.assertTrue(knownFolder.isKnown, `The ${knownFolderName} folder should have its "isKnown" property set to true.`); + TKUnit.assertNotNull(createdFile, `Could not create a new file in the ${knownFolderName} known folder.`); + TKUnit.assertTrue(fs.File.exists(createdFile.path), `Could not create a new file in the ${knownFolderName} known folder.`); + TKUnit.assertEqual(createdFile.readTextSync(), 'some text', `The contents of the new file created in the ${knownFolderName} known folder are not as expected.`); + } + } else { + TKUnit.assertThrows(testFunc, `Trying to retrieve the ${knownFolderName} known folder on a platform different from iOS should throw!`, `The "${knownFolderName}" known folder is available on iOS only!`); + } +} + +export var testIOSSpecificKnownFolders = function () { + _testIOSSpecificKnownFolder('library'); + _testIOSSpecificKnownFolder('developer'); + _testIOSSpecificKnownFolder('desktop'); + _testIOSSpecificKnownFolder('downloads'); + _testIOSSpecificKnownFolder('movies'); + _testIOSSpecificKnownFolder('music'); + _testIOSSpecificKnownFolder('pictures'); + _testIOSSpecificKnownFolder('sharedPublic'); +}; + +export var testGetEntities = function () { + // >> file-system-folders-content + var documents = fs.knownFolders.documents(); + // >> (hide) + var file = documents.getFile('Test.txt'); + var file1 = documents.getFile('Test1.txt'); + + var fileFound, file1Found; + + // IMPORTANT: console.log is mocked to make the snippet pretty. + var globalConsole = console; + var console = { + log: function (file) { + if (file === 'Test.txt') { + fileFound = true; + } else if (file === 'Test1.txt') { + file1Found = true; + } + }, + }; + + // << (hide) + documents.getEntities().then( + function (entities) { + // entities is array with the document's files and folders. + entities.forEach(function (entity) { + console.log(entity.name); + }); + // >> (hide) + + TKUnit.assert(fileFound, 'Failed to enumerate Test.txt'); + TKUnit.assert(file1Found, 'Failed to enumerate Test1.txt'); + + file.remove(); + file1.remove(); + // << (hide) + }, + function (error) { + // Failed to obtain folder's contents. + // globalConsole.error(error.message); + } + ); + // << file-system-folders-content +}; + +export var testEnumEntities = function () { + // >> file-system-enum-content + var documents = fs.knownFolders.documents(); + // >> (hide) + var file = documents.getFile('Test.txt'); + var file1 = documents.getFile('Test1.txt'); + var testFolder = documents.getFolder('testFolder'); + var fileFound = false; + var file1Found = false; + var testFolderFound = false; + var console = { + log: function (file) { + if (file === 'Test.txt') { + fileFound = true; + } else if (file === 'Test1.txt') { + file1Found = true; + } else if (file === 'testFolder') { + testFolderFound = true; + } + }, + }; + // << (hide) + documents.eachEntity(function (entity) { + console.log(entity.name); + + // Return true to continue, or return false to stop the iteration. + return true; + }); + // >> (hide) + TKUnit.assert(fileFound, 'Failed to enumerate Test.txt'); + TKUnit.assert(file1Found, 'Failed to enumerate Test1.txt'); + TKUnit.assert(testFolderFound, 'Failed to enumerate testFolder'); + + file.remove(); + file1.remove(); + testFolder.remove(); + // << (hide) + // << file-system-enum-content +}; + +export var testGetParent = function () { + // >> file-system-parent + var documents = fs.knownFolders.documents(); + var file = documents.getFile('Test.txt'); + // >> (hide) + TKUnit.assert(file, 'Failed to create file in the Documents folder.'); + // << (hide) + // The parent folder of the file would be the documents folder. + var parent = file.parent; + // >> (hide) + TKUnit.assert(documents === parent, 'The parent folder should be the Documents folder.'); + file.remove(); + // << (hide) + // << file-system-parent +}; + +export var testFileNameExtension = function () { + // >> file-system-extension + var documents = fs.knownFolders.documents(); + var file = documents.getFile('Test.txt'); + // Getting the file name "Test.txt". + var fileName = file.name; + // Getting the file extension ".txt". + var fileExtension = file.extension; + // >> (hide) + TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.'); + TKUnit.assert(fileExtension === '.txt', 'Wrong extension.'); + file.remove(); + // << (hide) + // << file-system-extension +}; + +export var testFileExists = function () { + // >> file-system-fileexists + var documents = fs.knownFolders.documents(); + var filePath = fs.path.join(documents.path, 'Test.txt'); + var exists = fs.File.exists(filePath); + // >> (hide) + TKUnit.assert(!exists, 'File.exists API not working.'); + var file = documents.getFile('Test.txt'); + exists = fs.File.exists(file.path); + TKUnit.assert(exists, 'File.exists API not working.'); + file.remove(); + // << (hide) + // << file-system-fileexists +}; + +export var testFolderExists = function () { + // >> file-system-folderexists + var documents = fs.knownFolders.documents(); + var exists = fs.Folder.exists(documents.path); + // >> (hide) + TKUnit.assert(exists, 'Folder.exists API not working.'); + exists = fs.Folder.exists(documents.path + '_'); + TKUnit.assert(!exists, 'Folder.exists API not working.'); + // << (hide) + // << file-system-folderexists +}; + +export var testContainsFile = function () { + var folder = fs.knownFolders.documents(); + var file = folder.getFile('Test.txt'); + + var contains = folder.contains('Test.txt'); + TKUnit.assert(contains, 'Folder.contains API not working.'); + contains = folder.contains('Test_xxx.txt'); + TKUnit.assert(!contains, 'Folder.contains API not working.'); + + file.remove(); +}; + +export var testFileRename = function () { + // >> file-system-renaming + var documents = fs.knownFolders.documents(); + var file = documents.getFile('Test.txt'); + + file.rename('Test_renamed.txt').then( + function (result) { + // Successfully Renamed. + // >> (hide) + TKUnit.assert(file.name === 'Test_renamed.txt', 'File.rename API not working.'); + file.remove(); + documents.getFile('Test.txt').remove(); + // << (hide) + }, + function (error) { + // Failed to rename the file. + // >> (hide) + TKUnit.assert(false, 'Failed to rename file'); + // << (hide) + } + ); + // << file-system-renaming +}; + +export var testFolderRename = function () { + // >> file-system-renaming-folder + var folder = fs.knownFolders.documents(); + var myFolder = folder.getFolder('Test__'); + + myFolder.rename('Something').then( + function (result) { + // Successfully Renamed. + // >> (hide) + TKUnit.assert(myFolder.name === 'Something', 'Folder.rename API not working.'); + myFolder.remove(); + folder.getFolder('Test__').remove(); + // << (hide) + }, + function (error) { + // Failed to rename the folder. + // >> (hide) + TKUnit.assert(false, 'Folder.rename API not working.'); + // << (hide) + } + ); + // << file-system-renaming-folder +}; + +export var testFileRemove = function () { + // >> file-system-remove-file + var documents = fs.knownFolders.documents(); + var file = documents.getFile('AFileToRemove.txt'); + file.remove().then( + function (result) { + // Success removing the file. + // >> (hide) + TKUnit.assert(!fs.File.exists(file.path)); + // << (hide) + }, + function (error) { + // Failed to remove the file. + // >> (hide) + TKUnit.assert(false, 'File.remove API not working.'); + // << (hide) + } + ); + // << file-system-remove-file +}; + +export var testFolderRemove = function () { + // >> file-system-remove-folder + var documents = fs.knownFolders.documents(); + var file = documents.getFolder('AFolderToRemove'); + // Remove a folder and recursively its content. + file.remove().then( + function (result) { + // Success removing the folder. + // >> (hide) + TKUnit.assert(!fs.File.exists(file.path)); + // << (hide) + }, + function (error) { + // Failed to remove the folder. + // >> (hide) + TKUnit.assert(false, 'File.remove API not working.'); + // << (hide) + } + ); + // << file-system-remove-folder +}; + +export var testFolderClear = function () { + // >> file-system-clear-folder + var documents = fs.knownFolders.documents(); + var folder = documents.getFolder('testFolderEmpty'); + // >> (hide) + folder.getFile('Test1.txt'); + folder.getFile('Test2.txt'); + var subfolder = folder.getFolder('subfolder'); + var emptied; + // << (hide) + folder.clear().then( + function () { + // Successfully cleared the folder. + // >> (hide) + emptied = true; + // << (hide) + }, + function (error) { + // Failed to clear the folder. + // >> (hide) + TKUnit.assert(false, error.message); + // << (hide) + } + ); + // >> (hide) + folder.getEntities().then(function (entities) { + TKUnit.assertEqual(entities.length, 0, `${entities.length} entities left after clearing a folder.`); + folder.remove(); + }); + // << (hide) + // << file-system-clear-folder +}; + +// misc +export var testKnownFolderRename = function () { + // You can rename known folders in android - so skip this test. + if (!appModule.android) { + var folder = fs.knownFolders.documents(); + folder.rename('Something').then( + function (result) { + TKUnit.assert(false, 'Known folders should not be renamed.'); + }, + function (error) { + TKUnit.assert(true); + } + ); + } +}; + +export function testKnownFolderRemove(done) { + var result; + + var knownFolder = fs.knownFolders.temp(); + + knownFolder.remove().then( + function () { + done(new Error('Remove known folder should resolve as error.')); + }, + function (error) { + done(null); + } + ); +} + +export function test_FSEntity_Properties() { + var documents = fs.knownFolders.documents(); + var file = documents.getFile('Test_File.txt'); + + TKUnit.assert(file.extension === '.txt', 'FileEntity.extension not working.'); + TKUnit.assert(file.isLocked === false, 'FileEntity.isLocked not working.'); + TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.'); + TKUnit.assert(file.size === 0, 'FileEntity.size not working.'); + TKUnit.assert(file.name === 'Test_File.txt', 'FileEntity.name not working.'); + TKUnit.assert(file.parent === documents, 'FileEntity.parent not working.'); + + file.remove(); +} + +export function test_FileSize(done) { + var file = fs.knownFolders.documents().getFile('Test_File_Size.txt'); + file + .writeText('Hello World!') + .then(() => { + TKUnit.assert(file.size === 'Hello World!'.length); + + return file.remove(); + }) + .then(() => done()) + .catch(done); +} + +export function test_UnlockAfterWrite(done) { + var file = fs.knownFolders.documents().getFile('Test_File_Lock.txt'); + file + .writeText('Hello World!') + .then(() => { + return file.readText(); + }) + .then((value) => { + TKUnit.assert(value === 'Hello World!'); + + return file.remove(); + }) + .then(() => done()) + .catch(done); +} + +export function test_CreateParentOnNewFile(done) { + var documentsFolderName = fs.knownFolders.documents().path; + var tempFileName = fs.path.join(documentsFolderName, 'folder1', 'folder2', 'Test_File_Create_Parent.txt'); + var file = fs.File.fromPath(tempFileName); + file + .writeText('Hello World!') + .then(() => { + return fs.knownFolders.documents().getFolder('folder1').remove(); + }) + .then(() => done()) + .catch(done); +} + +export function test_FolderClear_RemovesEmptySubfolders(done) { + let documents = fs.knownFolders.documents(); + let rootFolder = documents.getFolder('rootFolder'); + let emptySubfolder = rootFolder.getFolder('emptySubfolder'); + TKUnit.assertTrue(fs.Folder.exists(emptySubfolder.path), 'emptySubfolder should exist before parent folder is cleared.'); + rootFolder + .clear() + .then(() => { + TKUnit.assertFalse(fs.File.exists(emptySubfolder.path), 'emptySubfolder should not exist after parent folder was cleared.'); + rootFolder.remove(); + done(); + }) + .catch(done); +} diff --git a/packages/core/__tests__/e2e/automated/app/file-system/file-system.md b/apps/automated/app/file-system/file-system.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/file-system/file-system.md rename to apps/automated/app/file-system/file-system.md diff --git a/packages/core/__tests__/e2e/automated/app/font/FontAwesome.ttf b/apps/automated/app/font/FontAwesome.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/font/FontAwesome.ttf rename to apps/automated/app/font/FontAwesome.ttf diff --git a/packages/core/__tests__/e2e/automated/app/fonts/Pacifico.ttf b/apps/automated/app/fonts/Pacifico.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fonts/Pacifico.ttf rename to apps/automated/app/fonts/Pacifico.ttf diff --git a/packages/core/__tests__/e2e/automated/app/fonts/Roboto-Bold.ttf b/apps/automated/app/fonts/Roboto-Bold.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fonts/Roboto-Bold.ttf rename to apps/automated/app/fonts/Roboto-Bold.ttf diff --git a/packages/core/__tests__/e2e/automated/app/fonts/Roboto-BoldItalic.ttf b/apps/automated/app/fonts/Roboto-BoldItalic.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fonts/Roboto-BoldItalic.ttf rename to apps/automated/app/fonts/Roboto-BoldItalic.ttf diff --git a/packages/core/__tests__/e2e/automated/app/fonts/Roboto-Italic.ttf b/apps/automated/app/fonts/Roboto-Italic.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fonts/Roboto-Italic.ttf rename to apps/automated/app/fonts/Roboto-Italic.ttf diff --git a/packages/core/__tests__/e2e/automated/app/fonts/Roboto-Regular.ttf b/apps/automated/app/fonts/Roboto-Regular.ttf similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fonts/Roboto-Regular.ttf rename to apps/automated/app/fonts/Roboto-Regular.ttf diff --git a/apps/automated/app/fps-meter/fps-meter-tests.ts b/apps/automated/app/fps-meter/fps-meter-tests.ts new file mode 100644 index 000000000..55e82c549 --- /dev/null +++ b/apps/automated/app/fps-meter/fps-meter-tests.ts @@ -0,0 +1,15 @@ +// >> fps-meter-require +import * as fpsMeter from '@nativescript/core/fps-meter'; +// << fps-meter-require + +export var test_DummyTestForSnippetOnly0 = function () { + // >> fps-meter-logging + var callbackId = fpsMeter.addCallback(function (fps: number, minFps: number) { + console.info('fps=' + fps + ' minFps=' + minFps); + }); + fpsMeter.start(); + ////... + fpsMeter.removeCallback(callbackId); + fpsMeter.stop(); + // << fps-meter-logging +}; diff --git a/packages/core/__tests__/e2e/automated/app/fps-meter/fps-meter.md b/apps/automated/app/fps-meter/fps-meter.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/fps-meter/fps-meter.md rename to apps/automated/app/fps-meter/fps-meter.md diff --git a/apps/automated/app/globals/globals-tests.ts b/apps/automated/app/globals/globals-tests.ts new file mode 100644 index 000000000..987480f31 --- /dev/null +++ b/apps/automated/app/globals/globals-tests.ts @@ -0,0 +1,48 @@ +import '@nativescript/core/globals'; +import * as TKUnit from '../tk-unit'; + +declare var System: any; +export function test_global_system_import() { + TKUnit.assert(System, 'System not defined'); + TKUnit.assert(typeof System.import === 'function', 'System.import not a function'); + + TKUnit.assert((global).System, 'global.System not defined'); + TKUnit.assert(typeof (global).System.import === 'function', 'global.System.import not a function'); +} + +export function test_global_zonedCallback() { + TKUnit.assert(typeof zonedCallback === 'function', 'zonedCallback not defined'); + TKUnit.assert(typeof global.zonedCallback === 'function', 'global.zonedCallback not a function'); +} + +export function test_global_moduleMerge() { + TKUnit.assert(typeof global.moduleMerge === 'function', 'global.moduleMerge not a function'); +} + +export function test_global_registerModule() { + TKUnit.assert(typeof global.registerModule === 'function', 'global.registerModule not a function'); +} + +export function test_global_registerWebpackModules() { + TKUnit.assert(typeof global.registerWebpackModules === 'function', 'global.registerWebpackModules not a function'); +} + +export function test_global_loadModule() { + TKUnit.assert(typeof global.loadModule === 'function', 'global.loadModule not a function'); +} + +export function test_global_moduleExists() { + TKUnit.assert(typeof global.moduleExists === 'function', 'global.moduleExists not a function'); +} + +export function test_global_getRegisteredModules() { + TKUnit.assert(typeof global.getRegisteredModules === 'function', 'global.getRegisteredModules not a function'); +} + +export function test_global_Deprecated() { + TKUnit.assert(typeof global.Deprecated === 'function', 'global.Deprecated not a function'); +} + +export function test_global_Experimental() { + TKUnit.assert(typeof global.Experimental === 'function', 'global.Experimental not a function'); +} diff --git a/apps/automated/app/http/http-string-worker.ts b/apps/automated/app/http/http-string-worker.ts new file mode 100644 index 000000000..418217249 --- /dev/null +++ b/apps/automated/app/http/http-string-worker.ts @@ -0,0 +1,14 @@ +import * as http from '@nativescript/core/http'; + +(global).FormData = class FormData {}; + +declare var postMessage: any; + +http.getString('https://httpbin.org/get').then( + function (r) { + postMessage(r); + }, + function (e) { + throw e; + } +); diff --git a/apps/automated/app/http/http-tests.ts b/apps/automated/app/http/http-tests.ts new file mode 100644 index 000000000..3b52d07f4 --- /dev/null +++ b/apps/automated/app/http/http-tests.ts @@ -0,0 +1,703 @@ +import { ImageSource } from '@nativescript/core/image-source'; +import * as TKUnit from '../tk-unit'; +import * as http from '@nativescript/core/http'; +import * as fs from '@nativescript/core/file-system'; +import { addHeader } from '@nativescript/core/http/http-request'; + +export var test_getString_isDefined = function () { + TKUnit.assert(typeof http.getString !== 'undefined', 'Method http.getString() should be defined!'); +}; + +export var test_getString = function (done: (err: Error, res?: string) => void) { + http.getString('https://httpbin.org/get').then( + function (r) { + //// Argument (r) is string! + done(null); + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_getString_fail = function (done) { + http.getString({ url: 'hgfttp://httpbin.org/get', method: 'GET', timeout: 2000 }).catch(function (e) { + done(null); + }); +}; + +// TODO: should this be kept? many decoders will decode the png data into text (even if it's gibberish) +// export var test_getString_fail_when_result_is_not_string = function (done) { +// var result; + +// http.getString({ url: "https://httpbin.org/image/png", method: "GET" }).then(function (e) { +// result = e; +// try { +// TKUnit.assert(result instanceof Error, "Result from getString().catch() should be Error! Current type is " + typeof result); +// done(null); +// } +// catch (err) { +// done(err); +// } +// }); +// }; + +export var test_getJSON_isDefined = function () { + TKUnit.assert(typeof http.getJSON !== 'undefined', 'Method http.getJSON() should be defined!'); +}; + +export var test_getJSON = function (done) { + var result; + + http.getJSON('https://httpbin.org/get').then( + function (r) { + //// Argument (r) is JSON! + //completed = true; + result = r; + try { + TKUnit.assert(typeof JSON.stringify(result) === 'string', 'Result from getJSON() should be valid JSON object!'); + done(null); + } catch (e) { + done(e); + } + done(null); + }, + function (e) { + //// Argument (e) is Error! + //console.log(e); + done(e); + } + ); +}; + +export var test_getJSON_fail = function (done) { + var result; + + http.getJSON({ url: 'hgfttp://httpbin.org/get', method: 'GET', timeout: 2000 }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getJSON().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_getJSON_fail_when_result_is_not_JSON = function (done) { + var result; + + http.getJSON({ url: 'https://httpbin.org/html', method: 'GET' }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getJSON().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_getJSONP = function (done) { + var result; + + http.getJSON('https://jsfiddle.net/echo/jsonp/').then( + function (r) { + result = r; + try { + TKUnit.assert(typeof JSON.stringify(result) === 'string', 'Result from getJSON() should be valid JSON object!'); + done(null); + } catch (e) { + done(e); + } + done(null); + }, + function (e) { + done(e); + } + ); +}; + +export var test_getJSON_fail_when_result_is_not_JSONP = function (done) { + var result; + + http.getJSON({ url: 'https://httpbin.org/html', method: 'GET' }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getJSON().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_gzip_request_explicit = function (done) { + var result; + + http + .request({ + url: 'https://postman-echo.com/gzip', + method: 'GET', + headers: { + 'Accept-Encoding': 'gzip', + }, + }) + .then( + function (r) { + result = r; + try { + TKUnit.assert(typeof JSON.stringify(result) === 'string', 'Result from gzipped stream should be valid JSON object!'); + done(null); + } catch (e) { + done(e); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_gzip_request_implicit = function (done) { + var result; + + http + .request({ + url: 'https://postman-echo.com/gzip', + method: 'GET', + }) + .then( + function (r) { + result = r; + try { + TKUnit.assert(typeof JSON.stringify(result) === 'string', 'Result from gzipped stream should be valid JSON object!'); + done(null); + } catch (e) { + done(e); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_getImage_isDefined = function () { + TKUnit.assert(typeof http.getImage !== 'undefined', 'Method http.getImage() should be defined!'); +}; + +export var test_getImage = function (done) { + var result; + + http.getImage('https://httpbin.org/image/png').then( + (r) => { + // Argument (r) is ImageSource! + result = r; + try { + TKUnit.assert(result instanceof ImageSource, 'Result from getImage() should be valid ImageSource object!'); + done(null); + } catch (err) { + done(err); + } + }, + (err) => { + // Argument (e) is Error! + done(err); + } + ); +}; + +export var test_getImage_fail = function (done) { + var result; + + http.getImage({ url: 'hgfttp://www.google.com/images/errors/logo_sm_2.png', method: 'GET', timeout: 2000 }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getImage().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_getImage_fail_when_result_is_not_image = function (done) { + var result; + + http.getImage({ url: 'https://httpbin.org/html', method: 'GET' }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getImage().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_getFile_isDefined = function () { + TKUnit.assert(typeof http.getFile !== 'undefined', 'Method http.getFile() should be defined!'); +}; + +export var test_getFile = function (done) { + var result; + + http.getFile('https://raw.githubusercontent.com/NativeScript/NativeScript/master/tests/app/logo.png').then( + function (r) { + //// Argument (r) is File! + result = r; + try { + TKUnit.assert(result instanceof fs.File, 'Result from getFile() should be valid File object!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_getContentAsFile = function (done) { + var result; + + var filePath = fs.path.join(fs.knownFolders.documents().path, 'test.png'); + http.getFile('https://httpbin.org/image/png?testQuery=query&anotherParam=param', filePath).then( + function (r) { + //// Argument (r) is File! + result = r; + try { + TKUnit.assert(result instanceof fs.File, 'Result from getFile() should be valid File object!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_getFile_fail = function (done) { + var result; + + http.getImage({ url: 'hgfttp://raw.githubusercontent.com/NativeScript/NativeScript/master/tests/app/logo.png', method: 'GET', timeout: 2000 }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from getFile().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_request_isDefined = function () { + TKUnit.assert(typeof http['request'] !== 'undefined', 'Method http.request() should be defined!'); +}; + +export var test_request_shouldFailIfOptionsUrlIsNotDefined = function (done) { + var result; + + http.request({ url: undefined, method: undefined }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from request().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_request_requestShouldTimeout = function (done) { + var result; + http.request({ url: 'https://10.255.255.1', method: 'GET', timeout: 500 }).catch(function (e) { + result = e; + try { + TKUnit.assert(result instanceof Error, 'Result from request().catch() should be Error! Current type is ' + typeof result); + done(null); + } catch (err) { + done(err); + } + }); +}; + +export var test_request_responseStatusCodeShouldBeDefined = function (done) { + var result: http.HttpResponse; + + http.request({ url: 'https://httpbin.org/get', method: 'GET' }).then( + function (response) { + //// Argument (response) is HttpResponse! + var statusCode = response.statusCode; + result = response; + try { + TKUnit.assert(typeof statusCode !== 'undefined', 'response.statusCode should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_headRequest_responseStatusCodeShouldBeDefined = function (done) { + http.request({ url: 'https://httpbin.org/get', method: 'HEAD' }).then( + function (response) { + try { + TKUnit.assert(typeof response.statusCode !== 'undefined', 'response.statusCode should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_responseHeadersShouldBeDefined = function (done) { + var result: http.HttpResponse; + + http.request({ url: 'https://httpbin.org/get', method: 'GET' }).then( + function (response) { + //// Argument (response) is HttpResponse! + //for (var header in response.headers) { + // console.log(header + ":" + response.headers[header]); + //} + result = response; + try { + TKUnit.assert(typeof result.headers !== 'undefined', 'response.headers should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_request_responseContentShouldBeDefined = function (done) { + var result: http.HttpResponse; + + http.request({ url: 'https://httpbin.org/get', method: 'GET' }).then( + function (response) { + //// Argument (response) is HttpResponse! + //// Content property of the response is HttpContent! + result = response; + try { + TKUnit.assert(typeof result.content !== 'undefined', 'response.content should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + } + ); +}; + +export var test_request_responseContentToStringShouldReturnString = function (done) { + var result; + + http.request({ url: 'https://httpbin.org/get', method: 'GET' }).then( + function (response) { + result = response.content.toString(); + try { + TKUnit.assert(typeof result === 'string', 'Result from toString() should be string!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_responseContentToJSONShouldReturnJSON = function (done) { + var result; + + http.request({ url: 'https://httpbin.org/get', method: 'GET' }).then( + function (response) { + result = response.content.toJSON(); + try { + TKUnit.assert(typeof JSON.stringify(result) === 'string', 'Result from toJSON() should be valid JSON object!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_responseContentToImageShouldReturnCorrectImage = function (done) { + var result; + + http.request({ url: 'https://httpbin.org/image/png', method: 'GET' }).then( + function (response) { + response.content.toImage().then((source) => { + result = source; + try { + TKUnit.assert(result instanceof ImageSource, 'Result from toImage() should be valid promise of ImageSource object!'); + done(null); + } catch (err) { + done(err); + } + }); + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_responseContentToFileFromUrlShouldReturnCorrectFile = function (done) { + var result; + + http.request({ url: 'https://raw.githubusercontent.com/NativeScript/NativeScript/master/tests/app/logo.png', method: 'GET' }).then( + function (response) { + result = response.content.toFile(); + try { + TKUnit.assert(result instanceof fs.File, 'Result from toFile() should be valid File object!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; +export var test_request_responseContentToFileFromUrlShouldReturnCorrectFileAndCreateDirPathIfNecesary = function (done) { + var result; + + http.request({ url: 'https://raw.githubusercontent.com/NativeScript/NativeScript/master/tests/app/logo.png', method: 'GET' }).then( + function (response) { + const filePath = fs.path.join(fs.knownFolders.temp().path, 'test', 'some', 'path', 'logo.png'); + result = response.content.toFile(filePath); + try { + TKUnit.assert(result instanceof fs.File, 'Result from toFile() should be valid File object!'); + TKUnit.assert(result.size > 0, 'result from to file should be greater than 0 in size'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_responseContentToFileFromContentShouldReturnCorrectFile = function (done) { + var result; + + http.request({ url: 'https://httpbin.org/image/png?queryString=param&another=anotherParam', method: 'GET' }).then( + function (response) { + result = response.content.toFile(); + try { + TKUnit.assert(result instanceof fs.File, 'Result from toFile() should be valid File object!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_headersSentAndReceivedProperly = function (done) { + var result; + + http + .request({ + url: 'https://httpbin.org/get', + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }) + .then( + function (response) { + result = response.headers; + try { + TKUnit.assert(result['Content-Type'] === 'application/json', 'Headers not sent/received properly!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_headersWithSameKeyAddedProperly = function (done) { + var keyName = 'key'; + var value1 = 'value1'; + var value2 = 'value2'; + + var headers: http.Headers = {}; + + addHeader(headers, keyName, value1); + addHeader(headers, keyName, value2); + + try { + TKUnit.assertTrue(Array.isArray(headers[keyName])); + TKUnit.assertEqual(headers[keyName][0], value1); + TKUnit.assertEqual(headers[keyName][1], value2); + done(null); + } catch (err) { + done(err); + } +}; + +export var test_request_contentSentAndReceivedProperly = function (done) { + var result; + + http + .request({ + url: 'https://httpbin.org/post', + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + content: 'MyVariableOne=ValueOne&MyVariableTwo=ValueTwo', + }) + .then( + function (response) { + result = response.content.toJSON(); + try { + TKUnit.assert(result['form']['MyVariableOne'] === 'ValueOne' && result['form']['MyVariableTwo'] === 'ValueTwo', 'Content not sent/received properly!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_FormDataContentSentAndReceivedProperly = function (done) { + var result; + + var data = new FormData(); + data.append('MyVariableOne', 'ValueOne'); + data.append('MyVariableTwo', 'ValueTwo'); + + http + .request({ + url: 'https://httpbin.org/post', + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + content: data, + }) + .then( + function (response) { + result = response.content.toJSON(); + try { + TKUnit.assert(result['form']['MyVariableOne'] === 'ValueOne' && result['form']['MyVariableTwo'] === 'ValueTwo', 'Content not sent/received properly!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_NonStringHeadersSentAndReceivedProperly = function (done) { + var result; + + var postData = 'MyVariableOne=ValueOne&MyVariableTwo=ValueTwo'; + + http + .request({ + url: 'https://httpbin.org/post', + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': postData.length }, + content: postData, + }) + .then( + function (response) { + result = response.content.toJSON(); + try { + TKUnit.assert(result['form']['MyVariableOne'] === 'ValueOne' && result['form']['MyVariableTwo'] === 'ValueTwo', 'Content not sent/received properly!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + done(e); + } + ); +}; + +export var test_request_jsonAsContentSentAndReceivedProperly = function (done) { + var result; + + http + .request({ + url: 'https://httpbin.org/post', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + content: JSON.stringify({ MyVariableOne: 'ValueOne', MyVariableTwo: 'ValueTwo' }), + }) + .then( + function (response) { + // result = response.content.toJSON(); + result = response.content.toJSON(); + try { + TKUnit.assert(result['json']['MyVariableOne'] === 'ValueOne' && result['json']['MyVariableTwo'] === 'ValueTwo', 'Content not sent/received properly!'); + done(null); + } catch (err) { + done(err); + } + // console.log(result); + }, + function (e) { + done(e); + // console.log("Error occurred " + e); + } + ); +}; + +export var test_getString_WorksProperlyInWorker = function (done) { + const HttpStringWorker = require('nativescript-worker-loader!./http-string-worker'); + let worker = new HttpStringWorker(); + console.log('Worker Created'); + worker.onmessage = function (msg) { + console.log('Message received'); + done(); + }; + worker.onerror = function (e) { + console.log('errir received'); + done(e); + }; +}; diff --git a/packages/core/__tests__/e2e/automated/app/http/http.md b/apps/automated/app/http/http.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/http/http.md rename to apps/automated/app/http/http.md diff --git a/apps/automated/app/image-source/image-source-snippet.ts b/apps/automated/app/image-source/image-source-snippet.ts new file mode 100644 index 000000000..96b81f9fb --- /dev/null +++ b/apps/automated/app/image-source/image-source-snippet.ts @@ -0,0 +1,16 @@ +import { ImageSource } from '@nativescript/core/image-source'; +import * as fs from '@nativescript/core/file-system'; +// >> imagesource-from-imageasset-save-to + +export function imageSourceFromAsset(imageAsset) { + ImageSource.fromAsset(imageAsset).then((imageSource) => { + let folder = fs.knownFolders.documents().path; + let fileName = 'test.png'; + let path = fs.path.join(folder, fileName); + let saved = imageSource.saveToFile(path, 'png'); + if (saved) { + console.log('Image saved successfully!'); + } + }); +} +// << imagesource-from-imageasset-save-to diff --git a/apps/automated/app/image-source/image-source-tests.ts b/apps/automated/app/image-source/image-source-tests.ts new file mode 100644 index 000000000..5178cbc4f --- /dev/null +++ b/apps/automated/app/image-source/image-source-tests.ts @@ -0,0 +1,315 @@ +import { ImageSource } from '@nativescript/core/image-source'; +import * as imageAssetModule from '@nativescript/core/image-asset'; +import * as fs from '@nativescript/core/file-system'; +import * as app from '@nativescript/core/application'; +import * as TKUnit from '../tk-unit'; +import { Font } from '@nativescript/core/ui/styling/font'; +import { Color } from '@nativescript/core/color'; + +const imagePath = '~/assets/logo.png'; +const splashscreenPath = '~/assets/splashscreen.png'; +const splashscreenWidth = 372; +const splashscreenHeight = 218; +const smallImagePath = '~/assets/small-image.png'; + +export function testFromResource() { + // >> imagesource-resname + const img = ImageSource.fromResourceSync('icon'); + // << imagesource-resname + + TKUnit.assert(img.height > 0, 'image.fromResource failed'); +} + +export function testFromUrl(done) { + let result: ImageSource; + + // Deprecated method fromUrl + ImageSource.fromUrl('https://www.google.com/images/errors/logo_sm_2.png').then( + (res: ImageSource) => { + // console.log("Image successfully loaded"); + // completed = true; + result = res; + try { + TKUnit.assertNotEqual(result, undefined, 'Image not downloaded'); + TKUnit.assert(result.height > 0, 'Image not downloaded'); + done(null); + } catch (e) { + done(e); + } + }, + (error) => { + // console.log("Error loading image: " + error); + //completed = true; + done(error); + } + ); +} + +export function testSaveToFile() { + // >> imagesource-save-to + const img = ImageSource.fromFileSync(imagePath); + const folder = fs.knownFolders.documents(); + const path = fs.path.join(folder.path, 'test.png'); + const saved = img.saveToFile(path, 'png'); + // << imagesource-save-to + TKUnit.assert(saved, 'Image not saved to file'); + TKUnit.assert(fs.File.exists(path), 'Image not saved to file'); +} + +export function testSaveToFile_WithQuality() { + const img = ImageSource.fromFileSync(imagePath); + const folder = fs.knownFolders.documents(); + const path = fs.path.join(folder.path, 'test.png'); + const saved = img.saveToFile(path, 'png', 70); + TKUnit.assert(saved, 'Image not saved to file'); + TKUnit.assert(fs.File.exists(path), 'Image not saved to file'); +} + +export function testFromFile() { + // >> imagesource-load-local + const folder = fs.knownFolders.documents(); + const path = fs.path.join(folder.path, 'test.png'); + const img = ImageSource.fromFileSync(path); + // << imagesource-load-local + + TKUnit.assert(img.height > 0, 'image.fromResource failed'); + + // remove the image from the file system + const file = folder.getFile('test.png'); + file.remove(); + TKUnit.assert(!fs.File.exists(path), 'test.png not removed'); +} + +export function testFromAssetFileNotFound(done) { + let asset = new imageAssetModule.ImageAsset('invalidFile.png'); + asset.options = { + width: 0, + height: 0, + keepAspectRatio: true, + }; + + ImageSource.fromAsset(asset).then( + (source) => { + done('Should not resolve with invalid file name.'); + }, + (error) => { + TKUnit.assertNotNull(error); + done(); + } + ); +} + +export function testFromAssetSimple(done) { + let asset = new imageAssetModule.ImageAsset(splashscreenPath); + asset.options = { + width: 0, + height: 0, + keepAspectRatio: true, + }; + + ImageSource.fromAsset(asset).then( + (source) => { + TKUnit.assertEqual(source.width, splashscreenWidth); + TKUnit.assertEqual(source.height, splashscreenHeight); + done(); + }, + (error) => { + done(error); + } + ); +} + +export function testFromAssetWithExactScaling(done) { + let asset = new imageAssetModule.ImageAsset(splashscreenPath); + let scaleWidth = 10; + let scaleHeight = 11; + asset.options = { + width: scaleWidth, + height: scaleHeight, + keepAspectRatio: false, + autoScaleFactor: false, + }; + + ImageSource.fromAsset(asset).then( + (source) => { + TKUnit.assertEqual(source.width, scaleWidth); + TKUnit.assertEqual(source.height, scaleHeight); + + const targetFilename = `splashscreenTemp.png`; + const tempPath = fs.knownFolders.temp().path; + const localFullPath = fs.path.join(tempPath, targetFilename); + + const fullImageSaved = source.saveToFile(localFullPath, 'png'); + + if (fullImageSaved) { + ImageSource.fromFile(localFullPath).then((sourceImage) => { + TKUnit.assertEqual(sourceImage.width, scaleWidth); + TKUnit.assertEqual(sourceImage.height, scaleHeight); + done(); + }); + } else { + done(`Error saving photo to local temp folder: ${localFullPath}`); + } + }, + (error) => { + done(error); + } + ); +} + +export function testFromAssetWithScalingAndAspectRatio(done) { + let asset = new imageAssetModule.ImageAsset(splashscreenPath); + let scaleWidth = 10; + let scaleHeight = 11; + asset.options = { + width: scaleWidth, + height: scaleHeight, + keepAspectRatio: true, + }; + + ImageSource.fromAsset(asset).then( + (source) => { + TKUnit.assertEqual(source.width, 18); + TKUnit.assertEqual(source.height, scaleHeight); + done(); + }, + (error) => { + done(error); + } + ); +} + +export function testFromAssetWithScalingAndDefaultAspectRatio(done) { + let asset = new imageAssetModule.ImageAsset(splashscreenPath); + let scaleWidth = 10; + let scaleHeight = 11; + asset.options.width = scaleWidth; + asset.options.height = scaleHeight; + + ImageSource.fromAsset(asset).then( + (source) => { + TKUnit.assertEqual(source.width, 18); + TKUnit.assertEqual(source.height, scaleHeight); + done(); + }, + (error) => { + done(error); + } + ); +} + +export function testFromAssetWithBiggerScaling(done) { + let asset = new imageAssetModule.ImageAsset(splashscreenPath); + let scaleWidth = 600; + let scaleHeight = 600; + asset.options = { + width: scaleWidth, + height: scaleHeight, + keepAspectRatio: false, + }; + + ImageSource.fromAsset(asset).then( + (source) => { + TKUnit.assertEqual(source.width, scaleWidth); + TKUnit.assertEqual(source.height, scaleHeight); + done(); + }, + (error) => { + done(error); + } + ); +} + +export function testNativeFields() { + const img = ImageSource.fromFileSync(imagePath); + if (app.android) { + TKUnit.assert(img.android != null, 'Image.android not updated.'); + } else if (app.ios) { + TKUnit.assert(img.ios != null, 'Image.ios not updated.'); + } +} +const fullAndroidPng = 'iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAA3NCSVQICAjb4U/gAAAAFUlEQVQImWP8z4AAjAz/kTnIPGQAAG86AwGcuMlCAAAAAElFTkSuQmCC'; +const fullIosPng = 'iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAAXNSR0IArs4c6QAAABxpRE9UAAAAAgAAAAAAAAACAAAAKAAAAAIAAAACAAAARiS4uJEAAAASSURBVBgZYvjPwABHSMz/DAAAAAD//0GWpK0AAAAOSURBVGNgYPiPhBgQAACEvQv1D5y/pAAAAABJRU5ErkJggg=='; + +const jpgImageAsBase64String = + '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAAEAAQDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+Pz/h5j+1Z/z9fBr/AMRt+AH/AM7uiiiv9fV9E36KOn/HMX0f+n/NlvDT/p3/ANUv/V3vrf8AP1nueaf8LOa9P+ZjjP8Ap3/0/wD6u99b/wD/2Q=='; +const expectedJpegStart = '/9j/4AAQSkZJRgAB'; +const expectedPngStart = 'iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAA'; + +export function testBase64Encode_PNG() { + // >> imagesource-to-base-string + const img = ImageSource.fromFileSync(smallImagePath); + let base64String = img.toBase64String('png'); + // << imagesource-to-base-string + + base64String = base64String.substr(0, expectedPngStart.length); + TKUnit.assertEqual(base64String, expectedPngStart, 'Base 64 encoded PNG'); +} + +export function testBase64Encode_PNG_WithQuality() { + const img = ImageSource.fromFileSync(smallImagePath); + let base64String = img.toBase64String('png', 80); + base64String = base64String.substr(0, expectedPngStart.length); + TKUnit.assertEqual(base64String, expectedPngStart, 'Base 64 encoded PNG'); +} + +export function testBase64Encode_JPEG() { + const img = ImageSource.fromFileSync(smallImagePath); + + let base64String = img.toBase64String('jpeg'); + base64String = base64String.substr(0, expectedJpegStart.length); + + TKUnit.assertEqual(base64String, expectedJpegStart, 'Base 64 encoded JPEG'); +} + +export function testBase64Encode_JPEG_With_Quality() { + const img = ImageSource.fromFileSync(smallImagePath); + + let base64String = img.toBase64String('jpeg', 80); + base64String = base64String.substr(0, expectedJpegStart.length); + + TKUnit.assertEqual(base64String, expectedJpegStart, 'Base 64 encoded JPEG'); +} + +export function testLoadFromBase64Encode_JPEG() { + // >> imagesource-from-base-string + let img: ImageSource; + img = ImageSource.fromBase64Sync(jpgImageAsBase64String); + // << imagesource-from-base-string + + TKUnit.assert(img !== null, 'Actual: ' + img); + TKUnit.assertEqual(img.width, 4, 'img.width'); + TKUnit.assertEqual(img.height, 4, 'img.height'); +} + +export function testLoadFromBase64Encode_PNG() { + let img: ImageSource; + if (app.android) { + img = ImageSource.fromBase64Sync(fullAndroidPng); + } else if (app.ios) { + img = ImageSource.fromBase64Sync(fullIosPng); + } + + TKUnit.assert(img !== null, 'Actual: ' + img); + TKUnit.assertEqual(img.width, 4, 'img.width'); + TKUnit.assertEqual(img.height, 4, 'img.height'); +} + +export function testLoadFromFontIconCode() { + let img: ImageSource; + img = ImageSource.fromFontIconCodeSync('F10B', Font.default.withFontFamily('FontAwesome'), new Color('red')); + + TKUnit.assert(img !== null, 'Actual: ' + img); + TKUnit.assert(img.width !== null, 'img.width'); + TKUnit.assert(img.height !== null, 'img.width'); +} + +export function testResize() { + const img = ImageSource.fromFileSync(imagePath); + + const newSize = Math.floor(Math.max(img.width, img.height) / 2); + + const resized = img.resize(newSize); + + TKUnit.assert(resized.width === newSize || resized.height === newSize, 'Image not resized correctly'); +} diff --git a/packages/core/__tests__/e2e/automated/app/image-source/image-source.md b/apps/automated/app/image-source/image-source.md similarity index 100% rename from packages/core/__tests__/e2e/automated/app/image-source/image-source.md rename to apps/automated/app/image-source/image-source.md diff --git a/packages/core/__tests__/e2e/automated/app/livesync/app-new-page.css b/apps/automated/app/livesync/app-new-page.css similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/app-new-page.css rename to apps/automated/app/livesync/app-new-page.css diff --git a/packages/core/__tests__/e2e/automated/app/livesync/app-new-scss-page.scss b/apps/automated/app/livesync/app-new-scss-page.scss similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/app-new-scss-page.scss rename to apps/automated/app/livesync/app-new-scss-page.scss diff --git a/packages/core/__tests__/e2e/automated/app/livesync/application-page.css b/apps/automated/app/livesync/application-page.css similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/application-page.css rename to apps/automated/app/livesync/application-page.css diff --git a/packages/core/__tests__/e2e/automated/app/livesync/button-css-page.css b/apps/automated/app/livesync/button-css-page.css similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/button-css-page.css rename to apps/automated/app/livesync/button-css-page.css diff --git a/packages/core/__tests__/e2e/automated/app/livesync/button-scss-page.scss b/apps/automated/app/livesync/button-scss-page.scss similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/button-scss-page.scss rename to apps/automated/app/livesync/button-scss-page.scss diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-button-page.scss b/apps/automated/app/livesync/livesync-button-page.scss similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-button-page.scss rename to apps/automated/app/livesync/livesync-button-page.scss diff --git a/apps/automated/app/livesync/livesync-button-page.ts b/apps/automated/app/livesync/livesync-button-page.ts new file mode 100644 index 000000000..edaf318c0 --- /dev/null +++ b/apps/automated/app/livesync/livesync-button-page.ts @@ -0,0 +1,3 @@ +export function onLoaded() { + console.log('Button page loaded!'); +} diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-button-page.xml b/apps/automated/app/livesync/livesync-button-page.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-button-page.xml rename to apps/automated/app/livesync/livesync-button-page.xml diff --git a/apps/automated/app/livesync/livesync-label-page.ts b/apps/automated/app/livesync/livesync-label-page.ts new file mode 100644 index 000000000..a163d6161 --- /dev/null +++ b/apps/automated/app/livesync/livesync-label-page.ts @@ -0,0 +1,3 @@ +export function onLoaded() { + console.log('Label page loaded!'); +} diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-label-page.xml b/apps/automated/app/livesync/livesync-label-page.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-label-page.xml rename to apps/automated/app/livesync/livesync-label-page.xml diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.css b/apps/automated/app/livesync/livesync-modal-view-page.css similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.css rename to apps/automated/app/livesync/livesync-modal-view-page.css diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.scss b/apps/automated/app/livesync/livesync-modal-view-page.scss similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.scss rename to apps/automated/app/livesync/livesync-modal-view-page.scss diff --git a/apps/automated/app/livesync/livesync-modal-view-page.ts b/apps/automated/app/livesync/livesync-modal-view-page.ts new file mode 100644 index 000000000..ccd6338cd --- /dev/null +++ b/apps/automated/app/livesync/livesync-modal-view-page.ts @@ -0,0 +1,15 @@ +import { View, ShowModalOptions } from '@nativescript/core'; +const LIVESYNC_FOLDER = 'livesync/'; +const buttonPageModuleName = `${LIVESYNC_FOLDER}livesync-button-page`; + +export function onLoaded(args) { + const view = args.object as View; + + let options: ShowModalOptions = { + context: 'context', + closeCallback: () => console.log('modal view closeCallback raised.'), + animated: false, + }; + + view.showModal(buttonPageModuleName, options); +} diff --git a/packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.xml b/apps/automated/app/livesync/livesync-modal-view-page.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/livesync/livesync-modal-view-page.xml rename to apps/automated/app/livesync/livesync-modal-view-page.xml diff --git a/apps/automated/app/livesync/livesync-tests.ts b/apps/automated/app/livesync/livesync-tests.ts new file mode 100644 index 000000000..668008ba2 --- /dev/null +++ b/apps/automated/app/livesync/livesync-tests.ts @@ -0,0 +1,258 @@ +import * as helper from '../ui-helper'; +import * as TKUnit from '../tk-unit'; + +import * as app from '@nativescript/core/application'; +import * as frame from '@nativescript/core/ui/frame'; +import { Color } from '@nativescript/core/color'; +import { Builder } from '@nativescript/core/ui/builder'; +import { Page } from '@nativescript/core/ui/page'; +import { Frame } from '@nativescript/core/ui/frame'; + +const LIVESYNC_FOLDER = 'livesync/'; + +const appCssFileName = `${LIVESYNC_FOLDER}application-page.css`; +const appNewCssFileName = `${LIVESYNC_FOLDER}app-new-page.css`; +// `.scss` module registers in webpack as `.css` +// https://github.com/NativeScript/NativeScript/blob/5.4.2/@nativescript/core/globals/globals.ts#L32-L33 +const appNewScssFileNameAsCss = `${LIVESYNC_FOLDER}app-new-scss-page.css`; +const appNewScssFileName = `${LIVESYNC_FOLDER}app-new-scss-page.scss`; + +const buttonCssModuleName = `${LIVESYNC_FOLDER}button-css-page`; +const buttonScssModuleName = `${LIVESYNC_FOLDER}button-scss-page`; +const buttonCssFileName = `${LIVESYNC_FOLDER}button-css-page.css`; +const buttonScssFileName = `${LIVESYNC_FOLDER}button-scss-page.scss`; + +const buttonPageModuleName = `${LIVESYNC_FOLDER}livesync-button-page`; +const buttonHtmlPageFileName = `${LIVESYNC_FOLDER}livesync-button-page.html`; +const buttonXmlPageFileName = `${LIVESYNC_FOLDER}livesync-button-page.xml`; +const buttonJsPageFileName = `${LIVESYNC_FOLDER}livesync-button-page.js`; +const buttonTsPageFileName = `${LIVESYNC_FOLDER}livesync-button-page.ts`; +const buttonScssPageFileName = `${LIVESYNC_FOLDER}livesync-button-page.scss`; +const labelPageModuleName = `${LIVESYNC_FOLDER}livesync-label-page`; + +const modalViewPageModuleName = `${LIVESYNC_FOLDER}livesync-modal-view-page`; +const modalViewXmlPageFileName = `${LIVESYNC_FOLDER}livesync-modal-view-page.xml`; +const modalViewJsPageFileName = `${LIVESYNC_FOLDER}livesync-modal-view-page.js`; +const modalViewTsPageFileName = `${LIVESYNC_FOLDER}livesync-modal-view-page.ts`; +const modalViewScssPageFileName = `${LIVESYNC_FOLDER}livesync-modal-view-page.scss`; +const modalViewCssFileName = `${LIVESYNC_FOLDER}livesync-modal-view-page.css`; + +const green = new Color('green'); + +export function setUp() { + const labelPage = Builder.createViewFromEntry({ moduleName: labelPageModuleName }); + helper.navigate(() => labelPage); +} + +export function tearDown() { + app.setCssFileName(appCssFileName); +} + +export function test_onLiveSync_ModuleContext_AppStyle_AppNewCss() { + _test_onLiveSync_ModuleContext_AppStyle(appNewCssFileName, appNewCssFileName); +} + +export function test_onLiveSync_ModuleContext_AppStyle_AppNewScss() { + _test_onLiveSync_ModuleContext_AppStyle(appNewScssFileNameAsCss, appNewScssFileName); +} + +export function test_onLiveSync_ModuleContext_Undefined() { + _test_onLiveSync_ModuleContext({ type: undefined, path: undefined }); +} + +export function test_onLiveSync_ModuleContext_PathUndefined() { + _test_onLiveSync_ModuleContext({ type: 'script', path: undefined }); +} + +export function test_onLiveSync_ModuleContext_Script_JsFile() { + _test_onLiveSync_ModuleReplace({ type: 'script', path: buttonJsPageFileName }); +} + +export function test_onLiveSync_ModuleContext_Script_TsFile() { + _test_onLiveSync_ModuleReplace({ type: 'script', path: buttonTsPageFileName }); +} + +export function test_onLiveSync_ModuleContext_Style_CssFile() { + _test_onLiveSync_ModuleContext_TypeStyle(buttonCssModuleName, buttonCssFileName); +} + +export function test_onLiveSync_ModuleContext_Style_ScssFile() { + _test_onLiveSync_ModuleContext_TypeStyle(buttonScssModuleName, buttonScssFileName); +} + +export function test_onLiveSync_ModuleContext_Markup_HtmlFile() { + _test_onLiveSync_ModuleReplace({ type: 'markup', path: buttonHtmlPageFileName }); +} + +export function test_onLiveSync_ModuleContext_Markup_XmlFile() { + _test_onLiveSync_ModuleReplace({ type: 'markup', path: buttonXmlPageFileName }); +} + +export function test_onLiveSync_ModuleContext_MarkupXml_ScriptTs_Files() { + _test_onLiveSync_ModuleReplace_Multiple([ + { type: 'script', path: buttonTsPageFileName }, + { type: 'markup', path: buttonXmlPageFileName }, + ]); +} + +export function test_onLiveSync_ModuleContext_MarkupXml_ScriptTs_StyleScss_Files() { + _test_onLiveSync_ModuleReplace_Multiple([ + { type: 'script', path: buttonTsPageFileName }, + { type: 'markup', path: buttonXmlPageFileName }, + { type: 'style', path: buttonScssPageFileName }, + ]); +} + +export function test_onLiveSync_ModuleContext_MarkupHtml_ScriptTs_Files() { + _test_onLiveSync_ModuleReplace_Multiple([ + { type: 'script', path: buttonTsPageFileName }, + { type: 'markup', path: buttonHtmlPageFileName }, + ]); +} + +export function test_onLiveSync_ModuleContext_MarkupHtml_ScriptTs_StyleScss_Files() { + _test_onLiveSync_ModuleReplace_Multiple([ + { type: 'script', path: buttonTsPageFileName }, + { type: 'markup', path: buttonHtmlPageFileName }, + { type: 'style', path: buttonScssPageFileName }, + ]); +} + +export function test_onLiveSync_ModalViewClosed_MarkupXml() { + _test_onLiveSync_ModalViewClosed({ type: 'markup', path: modalViewXmlPageFileName }); +} + +export function test_onLiveSync_ModalViewClosed_ScriptTs() { + _test_onLiveSync_ModalViewClosed({ type: 'script', path: modalViewTsPageFileName }); +} + +export function test_onLiveSync_ModalViewClosed_ScriptJs() { + _test_onLiveSync_ModalViewClosed({ type: 'script', path: modalViewJsPageFileName }); +} + +export function test_onLiveSync_ModalViewClosed_StyleCss() { + _test_onLiveSync_ModalViewClosed({ type: 'style', path: modalViewCssFileName }); +} + +export function test_onLiveSync_ModalViewClosed_StyleScss() { + _test_onLiveSync_ModalViewClosed({ type: 'style', path: modalViewScssPageFileName }); +} + +function _test_onLiveSync_ModuleContext_AppStyle(appStyleFileName: string, livesyncStyleFileName: string) { + const pageBeforeNavigation = helper.getCurrentPage(); + const buttonPage = Builder.createViewFromEntry({ moduleName: buttonPageModuleName }); + helper.navigateWithHistory(() => buttonPage); + + app.setCssFileName(appStyleFileName); + const pageBeforeLiveSync = helper.getCurrentPage(); + livesync({ type: 'style', path: livesyncStyleFileName }); + + const pageAfterLiveSync = helper.getCurrentPage(); + TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById('button').style.color.toString() === green.toString()); + TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), 'Can NOT go back!'); + TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, 'Pages are different!'); + TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), 'Latest selectors version is NOT applied!'); + + helper.goBack(); + const pageAfterNavigationBack = helper.getCurrentPage(); + TKUnit.assertEqual(pageAfterNavigationBack.getViewById('label').style.color, green, 'App styles NOT applied on back navigation!'); + TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, 'Pages are different'); + TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), 'Latest selectors version is NOT applied!'); +} + +function _test_onLiveSync_ModuleContext(context: ModuleContext) { + const buttonPage = Builder.createViewFromEntry({ moduleName: buttonPageModuleName }); + helper.navigateWithHistory(() => buttonPage); + livesync({ type: context.type, path: context.path }); + + TKUnit.waitUntilReady(() => !!Frame.topmost()); + const topmostFrame = Frame.topmost(); + TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && !topmostFrame.canGoBack()); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById('label').isLoaded); +} + +function _test_onLiveSync_ModuleReplace(context: ModuleContext) { + const pageBeforeNavigation = helper.getCurrentPage(); + const buttonPage = Builder.createViewFromEntry({ moduleName: buttonPageModuleName }); + helper.navigateWithHistory(() => buttonPage); + + livesync({ type: context.type, path: context.path }); + const topmostFrame = Frame.topmost(); + waitUntilLivesyncComplete(topmostFrame); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById('button').isLoaded, 'Button page is NOT loaded!'); + TKUnit.assertEqual(topmostFrame.backStack.length, 1, 'Backstack is clean!'); + TKUnit.assertTrue(topmostFrame.canGoBack(), 'Can NOT go back!'); + + helper.goBack(); + const pageAfterBackNavigation = helper.getCurrentPage(); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById('label').isLoaded, 'Label page is NOT loaded!'); + TKUnit.assertEqual(topmostFrame.backStack.length, 0, 'Backstack is NOT clean!'); + TKUnit.assertEqual(pageBeforeNavigation, pageAfterBackNavigation, 'Pages are different!'); +} + +function _test_onLiveSync_ModuleContext_TypeStyle(styleModuleName: string, livesyncStyleFileName: string) { + const pageBeforeNavigation = helper.getCurrentPage(); + const buttonPage = Builder.createViewFromEntry({ moduleName: buttonPageModuleName }); + helper.navigateWithHistory(() => buttonPage); + + const pageBeforeLiveSync = helper.getCurrentPage(); + pageBeforeLiveSync._moduleName = styleModuleName; + + livesync({ type: 'style', path: livesyncStyleFileName }); + const topmostFrame = Frame.topmost(); + waitUntilLivesyncComplete(topmostFrame); + + const pageAfterLiveSync = helper.getCurrentPage(); + TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById('button').style.color.toString() === green.toString()); + TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), 'Can NOT go back!'); + TKUnit.assertEqual(topmostFrame.backStack.length, 1, 'Backstack is clean!'); + TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), 'Latest selectors version is NOT applied!'); + + helper.goBack(); + const pageAfterNavigationBack = helper.getCurrentPage(); + TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, 'Pages are different!'); + TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), 'Latest selectors version is NOT applied!'); +} + +function _test_onLiveSync_ModuleReplace_Multiple(context: ModuleContext[]) { + const pageBeforeNavigation = helper.getCurrentPage(); + const buttonPage = Builder.createViewFromEntry({ moduleName: buttonPageModuleName }); + helper.navigateWithHistory(() => buttonPage); + + context.forEach((item) => { + livesync(item); + }); + + const topmostFrame = Frame.topmost(); + waitUntilLivesyncComplete(topmostFrame); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById('button').isLoaded, 'Button page is NOT loaded!'); + TKUnit.assertEqual(topmostFrame.backStack.length, 1, 'Backstack is clean!'); + TKUnit.assertTrue(topmostFrame.canGoBack(), 'Can NOT go back!'); + + helper.goBack(); + const pageAfterBackNavigation = helper.getCurrentPage(); + TKUnit.assertTrue(topmostFrame.currentPage.getViewById('label').isLoaded, 'Label page is NOT loaded!'); + TKUnit.assertEqual(topmostFrame.backStack.length, 0, 'Backstack is NOT clean!'); + TKUnit.assertEqual(pageBeforeNavigation, pageAfterBackNavigation, 'Pages are different!'); +} + +function _test_onLiveSync_ModalViewClosed(context: ModuleContext) { + const modalViewPage = Builder.createViewFromEntry({ moduleName: modalViewPageModuleName }); + helper.navigateWithHistory(() => modalViewPage); + livesync({ type: context.type, path: context.path }); + + TKUnit.waitUntilReady(() => !!Frame.topmost()); + const topmostFrame = Frame.topmost(); + TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && topmostFrame.canGoBack()); + + TKUnit.assertTrue(topmostFrame._getRootModalViews().length === 0); +} + +function livesync(context: ModuleContext) { + const ls = (global).__coreModulesLiveSync || global.__onLiveSync; + ls(context); +} + +function waitUntilLivesyncComplete(frame: Frame) { + TKUnit.waitUntilReady(() => frame.navigationQueueIsEmpty()); +} diff --git a/packages/core/__tests__/e2e/automated/app/main-page.ts b/apps/automated/app/main-page.ts similarity index 55% rename from packages/core/__tests__/e2e/automated/app/main-page.ts rename to apps/automated/app/main-page.ts index 331d189ab..5a62b4412 100644 --- a/packages/core/__tests__/e2e/automated/app/main-page.ts +++ b/apps/automated/app/main-page.ts @@ -1,11 +1,11 @@ -import { Trace, Page } from "@nativescript/core"; +import { Trace, Page } from '@nativescript/core'; -import * as tests from "./test-runner"; +import * as tests from './test-runner'; let executeTests = true; Trace.enable(); -Trace.addCategories(Trace.categories.Test + "," + Trace.categories.Error); +Trace.addCategories(Trace.categories.Test + ',' + Trace.categories.Error); // When debugging // Trace.setCategories(Trace.categories.concat( @@ -18,13 +18,13 @@ Trace.addCategories(Trace.categories.Test + "," + Trace.categories.Error); // )); function runTests() { - setTimeout(() => tests.runAll(""), 10); + setTimeout(() => tests.runAll(''), 10); } export function onNavigatedTo(args) { - args.object.off(Page.loadedEvent, onNavigatedTo); - if (executeTests) { - executeTests = false; - runTests(); - } + args.object.off(Page.loadedEvent, onNavigatedTo); + if (executeTests) { + executeTests = false; + runTests(); + } } diff --git a/packages/core/__tests__/e2e/automated/app/main-page.xml b/apps/automated/app/main-page.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/main-page.xml rename to apps/automated/app/main-page.xml diff --git a/packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/other.xml b/apps/automated/app/name-resolvers-tests/files/other.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/other.xml rename to apps/automated/app/name-resolvers-tests/files/other.xml diff --git a/packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.land.xml b/apps/automated/app/name-resolvers-tests/files/test.land.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.land.xml rename to apps/automated/app/name-resolvers-tests/files/test.land.xml diff --git a/packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.minWH600.xml b/apps/automated/app/name-resolvers-tests/files/test.minWH600.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.minWH600.xml rename to apps/automated/app/name-resolvers-tests/files/test.minWH600.xml diff --git a/packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.xml b/apps/automated/app/name-resolvers-tests/files/test.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/name-resolvers-tests/files/test.xml rename to apps/automated/app/name-resolvers-tests/files/test.xml diff --git a/apps/automated/app/name-resolvers-tests/module-name-resolver-tests.ts b/apps/automated/app/name-resolvers-tests/module-name-resolver-tests.ts new file mode 100644 index 000000000..e1c6a6c6a --- /dev/null +++ b/apps/automated/app/name-resolvers-tests/module-name-resolver-tests.ts @@ -0,0 +1,39 @@ +import * as TKUnit from '../tk-unit'; +import { ModuleNameResolver, ModuleListProvider } from '@nativescript/core'; + +import { androidPhonePortraitContext, androidPhoneLandscapeContext, androidTabletPortraitContext, iPhoneLandscapeContext, iPhonePortraitContext } from './qualifier-matcher-tests'; + +const testModule = 'name-resolvers-tests/files/test'; +const moduleProvider: ModuleListProvider = () => { + return ['name-resolvers-tests/files/other.xml', 'name-resolvers-tests/files/test.land.xml', 'name-resolvers-tests/files/test.minWH600.xml', 'name-resolvers-tests/files/test.xml']; +}; + +export function test_module_name_resolver_with_android_phone_portrait() { + const moduleResolver = new ModuleNameResolver(androidPhonePortraitContext, moduleProvider); + const result = moduleResolver.resolveModuleName(testModule, 'xml'); + TKUnit.assertEqual(result, testModule + '.xml'); +} + +export function test_module_name_resolver_with_android_phone_landscape() { + const moduleResolver = new ModuleNameResolver(androidPhoneLandscapeContext, moduleProvider); + const result = moduleResolver.resolveModuleName(testModule, 'xml'); + TKUnit.assertEqual(result, testModule + '.land.xml'); +} + +export function test_module_name_resolver_with_android_tablet_portrait() { + const moduleResolver = new ModuleNameResolver(androidTabletPortraitContext, moduleProvider); + const result = moduleResolver.resolveModuleName(testModule, 'xml'); + TKUnit.assertEqual(result, testModule + '.minWH600.xml'); +} + +export function test_module_name_resolver_with_ios_phone_landscape() { + const moduleResolver = new ModuleNameResolver(iPhoneLandscapeContext, moduleProvider); + const result = moduleResolver.resolveModuleName(testModule, 'xml'); + TKUnit.assertEqual(result, testModule + '.land.xml'); +} + +export function test_module_name_resolver_with_ios_phone_portrait() { + const moduleResolver = new ModuleNameResolver(iPhonePortraitContext, moduleProvider); + const result = moduleResolver.resolveModuleName(testModule, 'xml'); + TKUnit.assertEqual(result, testModule + '.xml'); +} diff --git a/apps/automated/app/name-resolvers-tests/qualifier-matcher-tests.ts b/apps/automated/app/name-resolvers-tests/qualifier-matcher-tests.ts new file mode 100644 index 000000000..979f2e897 --- /dev/null +++ b/apps/automated/app/name-resolvers-tests/qualifier-matcher-tests.ts @@ -0,0 +1,147 @@ +import * as TKUnit from '../tk-unit'; +import * as enums from '@nativescript/core/ui/enums'; +import { findMatch, PlatformContext } from '@nativescript/core/module-name-resolver/qualifier-matcher'; + +export const androidPhonePortraitContext: PlatformContext = { + width: 360, + height: 640, + deviceType: enums.DeviceType.Phone, + os: 'android', +}; + +export const androidPhoneLandscapeContext: PlatformContext = { + width: 640, + height: 360, + deviceType: enums.DeviceType.Phone, + os: 'android', +}; + +export const androidTabletPortraitContext: PlatformContext = { + width: 600, + height: 960, + deviceType: enums.DeviceType.Tablet, + os: 'android', +}; + +export const iPhonePortraitContext: PlatformContext = { + width: 320, + height: 480, + deviceType: enums.DeviceType.Phone, + os: 'ios', +}; + +export const iPhoneLandscapeContext: PlatformContext = { + width: 480, + height: 320, + deviceType: enums.DeviceType.Phone, + os: 'ios', +}; + +export function test_findFileMatch_fileName() { + var candidates: Array = ['test.xml', 'test2.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.xml'); +} + +export function test_findFileMatch_os_android() { + var candidates: Array = ['test.xml', 'test.ios.xml', 'test.android.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.android.xml'); +} + +export function test_findFileMatch_os_ios() { + var candidates: Array = ['test.xml', 'test.ios.xml', 'test.android.xml', 'other.xml']; + + findMatchTemplate(candidates, iPhonePortraitContext, 'test.ios.xml'); +} + +export function test_findFileMatch_os_fallback() { + var candidates: Array = ['test.xml', 'test.ios.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.xml'); +} + +export function test_findFileMatch_minWH_fallback() { + var candidates: Array = ['test.xml', 'test.minWH600.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.xml'); +} + +export function test_findFileMatch_minWH_best_value() { + var candidates: Array = ['test.xml', 'test.minWH400.xml', 'test.minWH500.xml', 'test.minWH600.xml', 'test.minWH700.xml', 'other.xml']; + + findMatchTemplate(candidates, androidTabletPortraitContext, 'test.minWH600.xml'); +} + +export function test_findFileMatch_minW_fallback() { + var candidates: Array = ['test.xml', 'test.minW600.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.xml'); +} + +export function test_findFileMatch_minW_best_value() { + var candidates: Array = ['test.xml', 'test.minW400.xml', 'test.minW500.xml', 'test.minW600.xml', 'test.minW700.xml', 'other.xml']; + + findMatchTemplate(candidates, androidTabletPortraitContext, 'test.minW600.xml'); +} + +export function test_findFileMatch_minH_fallback() { + var candidates: Array = ['test.xml', 'test.minH600.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhoneLandscapeContext, 'test.xml'); +} + +export function test_findFileMatch_minH_best_value() { + var candidates: Array = ['test.xml', 'test.minH400.xml', 'test.minH500.xml', 'test.minH600.xml', 'test.minH700.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.minH600.xml'); +} + +export function test_findFileMatch_orientation_fallback() { + var candidates: Array = ['test.xml', 'test.land.xml', 'other.xml']; + + findMatchTemplate(candidates, androidTabletPortraitContext, 'test.xml'); +} + +export function test_findFileMatch_orientation_portrait() { + var candidates: Array = ['test.xml', 'test.land.xml', 'test.port.xml', 'other.xml']; + + findMatchTemplate(candidates, androidTabletPortraitContext, 'test.port.xml'); +} + +export function test_findFileMatch_orientation_landscape() { + var candidates: Array = ['test.xml', 'test.land.xml', 'test.port.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhoneLandscapeContext, 'test.land.xml'); +} + +export function test_findFileMatch_choose_most_specific_file() { + var candidates: Array = ['test.xml', 'test.android.xml', 'test.android.port.xml', 'other.xml']; + + findMatchTemplate(candidates, androidPhonePortraitContext, 'test.android.port.xml'); +} + +export function test_findFileMatch_with_multiple_matches_loads_by_priority() { + var candidates: Array = ['test.xml', 'test.android.xml', 'test.tablet.xml', 'test.land.xml', 'test.minH600.xml', 'test.minW600.xml', 'test.minWH600.xml', 'other.xml']; + + findMatchTemplate(candidates, androidTabletPortraitContext, 'test.minWH600.xml'); +} + +function findMatchTemplate(candidates: Array, context: PlatformContext, expected: string) { + var result = findMatch('test', '.xml', candidates, context); + TKUnit.assertEqual(result, expected, 'module name'); +} + +export function test_findFileMatch_with_empty_extension() { + var candidates: Array = ['test', 'other']; + + var result = findMatch('test', '', candidates, androidTabletPortraitContext); + TKUnit.assertEqual(result, 'test', 'module name'); +} + +export function test_findFileMatch_with_null_extension() { + var candidates: Array = ['test', 'other']; + + var result = findMatch('test', null, candidates, androidTabletPortraitContext); + TKUnit.assertEqual(result, 'test', 'module name'); +} diff --git a/apps/automated/app/navigation/custom-transition.android.ts b/apps/automated/app/navigation/custom-transition.android.ts new file mode 100644 index 000000000..a7abf2dfa --- /dev/null +++ b/apps/automated/app/navigation/custom-transition.android.ts @@ -0,0 +1,36 @@ +import * as transition from '@nativescript/core/ui/transition'; + +export class CustomTransition extends transition.Transition { + constructor(duration: number, curve: any) { + super(duration, curve); + } + + public createAndroidAnimator(transitionType: string): android.animation.Animator { + var scaleValues = Array.create('float', 2); + switch (transitionType) { + case transition.AndroidTransitionType.enter: + case transition.AndroidTransitionType.popEnter: + scaleValues[0] = 0; + scaleValues[1] = 1; + break; + case transition.AndroidTransitionType.exit: + case transition.AndroidTransitionType.popExit: + scaleValues[0] = 1; + scaleValues[1] = 0; + break; + } + var objectAnimators = Array.create(android.animation.Animator, 2); + objectAnimators[0] = android.animation.ObjectAnimator.ofFloat(null, 'scaleX', scaleValues); + objectAnimators[1] = android.animation.ObjectAnimator.ofFloat(null, 'scaleY', scaleValues); + var animatorSet = new android.animation.AnimatorSet(); + animatorSet.playTogether(objectAnimators); + + var duration = this.getDuration(); + if (duration !== undefined) { + animatorSet.setDuration(duration); + } + animatorSet.setInterpolator(this.getCurve()); + + return animatorSet; + } +} diff --git a/apps/automated/app/navigation/custom-transition.d.ts b/apps/automated/app/navigation/custom-transition.d.ts new file mode 100644 index 000000000..0c470ea60 --- /dev/null +++ b/apps/automated/app/navigation/custom-transition.d.ts @@ -0,0 +1,6 @@ +import { Transition } from '@nativescript/core/ui/transition'; + +export class CustomTransition extends Transition { + constructor(); + constructor(duration: number, curve: any); +} diff --git a/apps/automated/app/navigation/custom-transition.ios.ts b/apps/automated/app/navigation/custom-transition.ios.ts new file mode 100644 index 000000000..e1143cd85 --- /dev/null +++ b/apps/automated/app/navigation/custom-transition.ios.ts @@ -0,0 +1,33 @@ +import * as transition from '@nativescript/core/ui/transition'; + +export class CustomTransition extends transition.Transition { + constructor(duration: number, curve: any) { + super(duration, curve); + } + + public animateIOSTransition(containerView: UIView, fromView: UIView, toView: UIView, operation: UINavigationControllerOperation, completion: (finished: boolean) => void): void { + toView.transform = CGAffineTransformMakeScale(0, 0); + fromView.transform = CGAffineTransformIdentity; + + switch (operation) { + case UINavigationControllerOperation.Push: + containerView.insertSubviewAboveSubview(toView, fromView); + break; + case UINavigationControllerOperation.Pop: + containerView.insertSubviewBelowSubview(toView, fromView); + break; + } + + var duration = this.getDuration(); + var curve = this.getCurve(); + UIView.animateWithDurationAnimationsCompletion( + duration, + () => { + UIView.setAnimationCurve(curve); + toView.transform = CGAffineTransformIdentity; + fromView.transform = CGAffineTransformMakeScale(0, 0); + }, + completion + ); + } +} diff --git a/apps/automated/app/navigation/navigation-tests.ts b/apps/automated/app/navigation/navigation-tests.ts new file mode 100644 index 000000000..b80ef3141 --- /dev/null +++ b/apps/automated/app/navigation/navigation-tests.ts @@ -0,0 +1,467 @@ +import * as TKUnit from '../tk-unit'; +import { EventData, Page, NavigatedData } from '@nativescript/core'; +import { Frame, NavigationTransition } from '@nativescript/core/ui/frame'; +import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; +import { Color } from '@nativescript/core/color'; +import * as helper from '../ui-helper'; +import * as frame from '@nativescript/core/ui/frame'; +// Creates a random colorful page full of meaningless stuff. +let id = 0; +let pageFactory = function (): Page { + const page = new Page(); + page.actionBarHidden = true; + page.id = `NavTestPage${id++}`; + page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return page; +}; + +function attachEventListeners(page: Page, events: Array) { + let argsToString = (args: NavigatedData) => { + return `${(args.object).id} ${args.eventName} ${args.isBackNavigation ? 'back' : 'forward'}`; + }; + + page.on(Page.navigatingFromEvent, (args: NavigatedData) => { + events.push(argsToString(args)); + }); + page.on(Page.navigatedFromEvent, (args: NavigatedData) => { + events.push(argsToString(args)); + }); + page.on(Page.navigatingToEvent, (args: NavigatedData) => { + events.push(argsToString(args)); + }); + page.on(Page.navigatedToEvent, (args: NavigatedData) => { + events.push(argsToString(args)); + }); +} + +function _test_backstackVisible(transition?: NavigationTransition) { + let topmost = Frame.topmost(); + let mainTestPage = topmost.currentPage; + topmost.navigate({ create: pageFactory, transition: transition, animated: !!transition }); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + // page1 should not be added to the backstack + let page0 = topmost.currentPage; + topmost.navigate({ create: pageFactory, backstackVisible: false, transition: transition, animated: !!transition }); + topmost.navigate({ create: pageFactory, transition: transition, animated: !!transition }); + topmost.goBack(); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + // From page2 we have to go directly to page0, skipping page1. + TKUnit.assertEqual(topmost.currentPage, page0, 'Page 1 should be skipped when going back.'); + + helper.goBack(); + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +export function test_backstackVisible() { + _test_backstackVisible(); +} + +export function test_backstackVisible_WithTransition() { + _test_backstackVisible({ name: 'fade', duration: 10 }); +} + +export function test_backAndForwardParentPage_nestedFrames() { + const topmost = Frame.topmost(); + const mainTestPage = topmost.currentPage; + let innerFrame; + + const page = (title) => { + const p = new Page(); + p['tag'] = title; + + return p; + }; + + const parentPage = (title, innerPage) => { + const parentPage = new Page(); + parentPage['tag'] = title; + + const stack = new StackLayout(); + innerFrame = new frame.Frame(); + innerFrame.navigate({ create: () => innerPage }); + stack.addChild(innerFrame); + parentPage.content = stack; + + return parentPage; + }; + + const back = (pages) => Frame.topmost().goBack(Frame.topmost().backStack[Frame.topmost().backStack.length - pages]); + const currentPageMustBe = (tag) => TKUnit.assertEqual(Frame.topmost().currentPage['tag'], tag, 'Expected current page to be ' + tag + ' it was ' + Frame.topmost().currentPage['tag'] + ' instead.'); + + let parentPage1, parentPage2, innerPage1, innerPage2; + innerPage1 = page('InnerPage1'); + innerPage2 = page('InnerPage2'); + parentPage1 = page('ParentPage1'); + parentPage2 = parentPage('ParentPage2', innerPage1); + + helper.waitUntilNavigatedTo(parentPage1, () => topmost.navigate({ create: () => parentPage1 })); + currentPageMustBe('ParentPage1'); + + helper.waitUntilNavigatedTo(parentPage2, () => topmost.navigate({ create: () => parentPage2 })); + currentPageMustBe('ParentPage2'); + + helper.waitUntilNavigatedTo(innerPage2, () => innerFrame.navigate({ create: () => innerPage2 })); + currentPageMustBe('InnerPage2'); + + helper.waitUntilNavigatedTo(innerPage1, () => Frame.goBack()); + currentPageMustBe('InnerPage1'); + + helper.waitUntilNavigatedTo(parentPage1, () => Frame.goBack()); + currentPageMustBe('ParentPage1'); + + const innerPage3 = page('InnerPage3'); + const parentPage3 = parentPage('ParentPage3', innerPage3); + + helper.waitUntilNavigatedTo(parentPage3, () => topmost.navigate({ create: () => parentPage3 })); + currentPageMustBe('ParentPage3'); + + back(2); + TKUnit.waitUntilReady(() => Frame.topmost().navigationQueueIsEmpty()); + + const frameStack = Frame._stack(); + TKUnit.assertEqual(frameStack.length, 1, 'There should be only one frame left in the stack'); + TKUnit.assertEqual(Frame.topmost().currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +function _test_backToEntry(transition?: NavigationTransition) { + const topmost = Frame.topmost(); + const page = (tag) => () => { + const p = new Page(); + p.actionBarHidden = true; + p.id = `NavTestPage${id++}`; + p['tag'] = tag; + + return p; + }; + + const mainTestPage = topmost.currentPage; + + const navigate = (tag) => topmost.navigate({ create: page(tag), transition: transition, animated: !!transition }); + const back = (pages) => topmost.goBack(topmost.backStack[topmost.backStack.length - pages]); + const currentPageMustBe = (tag) => TKUnit.assertEqual(topmost.currentPage['tag'], tag, 'Expected current page to be ' + tag + ' it was ' + topmost.currentPage['tag'] + ' instead.'); + + navigate('page1'); + navigate('page2'); + navigate('page3'); + navigate('page4'); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page4'); + + back(2); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page2'); + + back(1); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page1'); + + navigate('page1.1'); + navigate('page1.2'); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page1.2'); + + back(1); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page1.1'); + + back(1); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + currentPageMustBe('page1'); + + back(1); + TKUnit.waitUntilReady(() => topmost.navigationQueueIsEmpty()); + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +export function test_backToEntry() { + _test_backToEntry(); +} + +export function test_backToEntry_WithTransition() { + _test_backToEntry({ name: 'fade', duration: 10 }); +} + +function _test_ClearHistory(transition?: NavigationTransition) { + let topmost = Frame.topmost(); + + helper.navigateWithEntry({ create: pageFactory, clearHistory: true, transition: transition, animated: !!transition }); + TKUnit.assertEqual(topmost.backStack.length, 0, '1.topmost.backStack.length'); + TKUnit.assertEqual(topmost.canGoBack(), false, '1.topmost.canGoBack().'); + + helper.navigateWithEntry({ create: pageFactory, transition: transition, animated: !!transition }); + TKUnit.assertEqual(topmost.backStack.length, 1, '2.topmost.backStack.length'); + TKUnit.assertEqual(topmost.canGoBack(), true, '2.topmost.canGoBack().'); + + helper.navigateWithEntry({ create: pageFactory, transition: transition, animated: !!transition }); + TKUnit.assertEqual(topmost.backStack.length, 2, '3.topmost.backStack.length'); + TKUnit.assertEqual(topmost.canGoBack(), true, '3.topmost.canGoBack().'); + + helper.navigateWithEntry({ create: pageFactory, clearHistory: true, transition: transition, animated: !!transition }); + TKUnit.assertEqual(topmost.backStack.length, 0, '4.topmost.backStack.length'); + TKUnit.assertEqual(topmost.canGoBack(), false, '4.topmost.canGoBack().'); +} + +export function test_ClearHistory() { + _test_ClearHistory(); +} + +export function test_ClearHistory_WithTransition() { + _test_ClearHistory({ name: 'fade', duration: 10 }); +} + +// Test case for https://github.com/NativeScript/NativeScript/issues/1948 +export function test_ClearHistoryWithTransitionDoesNotBreakNavigation() { + let topmost = Frame.topmost(); + let mainTestPage = new Page(); + let mainPageFactory = function (): Page { + return mainTestPage; + }; + + // Go to details-page + helper.navigateWithEntry({ create: pageFactory, clearHistory: false, animated: true }); + + // Go back to main-page with clearHistory + topmost.transition = { name: 'fade', duration: 10 }; + helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, animated: true }); + + // Go to details-page AGAIN + helper.navigateWithEntry({ create: pageFactory, clearHistory: false, animated: true }); + + // Go back to main-page with clearHistory + topmost.transition = { name: 'fade', duration: 10 }; + helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, animated: true }); + + // Clean up + topmost.transition = undefined; + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); + TKUnit.assertEqual(topmost.backStack.length, 0, 'Back stack should be empty at the end of the test.'); +} + +export function test_ClearHistoryWithTransitionDoesNotBreakNavigation_WithLocalTransition() { + const topmost = Frame.topmost(); + + let mainTestPage = topmost.currentPage; + let mainPageFactory = function (): Page { + return mainTestPage; + }; + + // Go to 1st page + helper.navigateWithEntry({ create: pageFactory, clearHistory: false, transition: { name: 'fade', duration: 10 }, animated: true }); + + // Go to 2nd page + helper.navigateWithEntry({ create: pageFactory, clearHistory: false, transition: { name: 'fade', duration: 10 }, animated: true }); + + // Go to 3rd page with clearHistory + helper.navigateWithEntry({ create: pageFactory, clearHistory: true, transition: { name: 'fade', duration: 10 }, animated: true }); + + // Go back to main + helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, transition: { name: 'fade', duration: 10 }, animated: true }); + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); + TKUnit.assertEqual(topmost.backStack.length, 0, 'Back stack should be empty at the end of the test.'); +} + +function _test_NavigationEvents(transition?: NavigationTransition) { + const topmost = Frame.topmost(); + const mainTestPage = topmost.currentPage; + const originalMainPageId = mainTestPage.id; + + mainTestPage.id = 'main-page'; + let actualMainPageEvents = new Array(); + attachEventListeners(mainTestPage, actualMainPageEvents); + + let actualSecondPageEvents = new Array(); + let secondPageFactory = function (): Page { + const secondPage = new Page(); + secondPage.actionBarHidden = true; + secondPage.id = 'second-page'; + attachEventListeners(secondPage, actualSecondPageEvents); + secondPage.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return secondPage; + }; + + // Go to other page + helper.navigateWithEntry({ create: secondPageFactory, transition: transition, animated: !!transition }); + + // Go back to main + helper.goBack(); + + mainTestPage.id = originalMainPageId; + + let expectedMainPageEvents = ['main-page navigatingFrom forward', 'main-page navigatedFrom forward', 'main-page navigatingTo back', 'main-page navigatedTo back']; + TKUnit.arrayAssert(actualMainPageEvents, expectedMainPageEvents, 'Actual main-page events are different from expected.'); + + let expectedSecondPageEvents = ['second-page navigatingTo forward', 'second-page navigatedTo forward', 'second-page navigatingFrom back', 'second-page navigatedFrom back']; + TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, 'Actual second-page events are different from expected.'); + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +export function test_NavigationEvents() { + _test_NavigationEvents(); +} + +export function test_NavigationEvents_WithTransition() { + _test_NavigationEvents({ name: 'fade', duration: 10 }); +} + +function _test_NavigationEvents_WithBackstackVisibile_False_Forward_Back(transition?: NavigationTransition) { + const topmost = Frame.topmost(); + const mainTestPage = topmost.currentPage; + + let actualSecondPageEvents = new Array(); + let secondPageFactory = function (): Page { + const secondPage = new Page(); + secondPage.actionBarHidden = true; + secondPage.id = 'second-page'; + attachEventListeners(secondPage, actualSecondPageEvents); + secondPage.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return secondPage; + }; + + // Go to other page + helper.navigateWithEntry({ create: secondPageFactory, transition: transition, animated: !!transition, backstackVisible: false }); + + // Go back to main + helper.goBack(); + + let expectedSecondPageEvents = ['second-page navigatingTo forward', 'second-page navigatedTo forward', 'second-page navigatingFrom back', 'second-page navigatedFrom back']; + TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, 'Actual second-page events are different from expected.'); + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +export function test_NavigationEvents_WithBackstackVisibile_False_Forward_Back() { + _test_NavigationEvents_WithBackstackVisibile_False_Forward_Back(); +} + +export function test_NavigationEvents_WithBackstackVisibile_False_Forward_Back_WithTransition() { + _test_NavigationEvents_WithBackstackVisibile_False_Forward_Back({ name: 'fade', duration: 10 }); +} + +function _test_NavigationEvents_WithBackstackVisibile_False_Forward_Forward(transition?: NavigationTransition) { + const topmost = Frame.topmost(); + const mainTestPage = topmost.currentPage; + + let actualSecondPageEvents = new Array(); + let secondPageFactory = function (): Page { + const secondPage = new Page(); + secondPage.actionBarHidden = true; + secondPage.id = 'second-page'; + attachEventListeners(secondPage, actualSecondPageEvents); + secondPage.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return secondPage; + }; + + // Go to other page + helper.navigateWithEntry({ create: secondPageFactory, transition: transition, animated: !!transition, backstackVisible: false }); + + // Go forward to third page + helper.navigateWithEntry({ create: pageFactory, transition: transition, animated: !!transition }); + + // Go back to main + helper.goBack(); + + let expectedSecondPageEvents = ['second-page navigatingTo forward', 'second-page navigatedTo forward', 'second-page navigatingFrom forward', 'second-page navigatedFrom forward']; + TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, 'Actual second-page events are different from expected.'); + + TKUnit.assertEqual(topmost.currentPage, mainTestPage, 'We should be on the main test page at the end of the test.'); +} + +export function test_NavigationEvents_WithBackstackVisibile_False_Forward_Forward() { + _test_NavigationEvents_WithBackstackVisibile_False_Forward_Forward(); +} + +export function test_NavigationEvents_WithBackstackVisibile_False_Forward_Forward_WithTransition() { + _test_NavigationEvents_WithBackstackVisibile_False_Forward_Forward({ name: 'fade', duration: 10 }); +} + +function _test_NavigationEvents_WithClearHistory(transition?: NavigationTransition) { + const topmost = Frame.topmost(); + const mainTestPage = topmost.currentPage; + + mainTestPage.id = 'main-page'; + const actualMainPageEvents = new Array(); + attachEventListeners(mainTestPage, actualMainPageEvents); + + const actualSecondPageEvents = new Array(); + const secondPage = new Page(); + const secondPageFactory = function (): Page { + secondPage.actionBarHidden = true; + secondPage.id = 'second-page'; + attachEventListeners(secondPage, actualSecondPageEvents); + secondPage.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return secondPage; + }; + + // Go to second page + helper.navigateWithEntry({ create: secondPageFactory, transition: transition, animated: !!transition, clearHistory: true }); + + const expectedMainPageEvents = ['main-page navigatingFrom forward', 'main-page navigatedFrom forward']; + TKUnit.arrayAssert(actualMainPageEvents, expectedMainPageEvents, 'Actual main-page events are different from expected.'); + + const expectedSecondPageEvents = ['second-page navigatingTo forward', 'second-page navigatedTo forward']; + TKUnit.arrayAssert(actualSecondPageEvents, expectedSecondPageEvents, 'Actual main-page events are different from expected.'); + + TKUnit.assertEqual(topmost.currentPage, secondPage, 'We should be on the second page at the end of the test.'); +} + +export function test_NavigationEvents_WithClearHistory() { + _test_NavigationEvents_WithClearHistory(); +} + +export function test_NavigationEvents_WithClearHistory_WithTransition() { + _test_NavigationEvents_WithClearHistory({ name: 'fade', duration: 10 }); +} + +export function test_Navigate_From_Page_Loaded_Handler() { + _test_Navigate_From_Page_Event_Handler(Page.loadedEvent); +} + +export function test_Navigate_From_Page_NavigatedTo_Handler() { + _test_Navigate_From_Page_Event_Handler(Page.navigatedToEvent); +} + +function _test_Navigate_From_Page_Event_Handler(eventName: string) { + let secondPageNavigatedTo = false; + + const firstPageFactory = function (): Page { + const firstPage = new Page(); + firstPage.id = 'first-page'; + firstPage.on(eventName, (args: EventData) => { + const page = args.object; + const frame = page.frame; + + const secondPageFactory = function (): Page { + const secondPage = new Page(); + secondPage.id = 'second-page'; + secondPage.on(Page.navigatedToEvent, () => { + secondPageNavigatedTo = true; + }); + + return secondPage; + }; + + frame.navigate(secondPageFactory); + }); + + return firstPage; + }; + + helper.navigateWithEntry({ create: firstPageFactory }); + + TKUnit.waitUntilReady(() => secondPageNavigatedTo); +} diff --git a/apps/automated/app/navigation/transition-tests.ts b/apps/automated/app/navigation/transition-tests.ts new file mode 100644 index 000000000..4ace09557 --- /dev/null +++ b/apps/automated/app/navigation/transition-tests.ts @@ -0,0 +1,58 @@ +import * as helper from '../ui-helper'; +import * as platform from '@nativescript/core/platform'; +import { Trace } from '@nativescript/core'; +import { Color } from '@nativescript/core/color'; +import { NavigationEntry, NavigationTransition } from '@nativescript/core/ui/frame'; +import { Page } from '@nativescript/core/ui/page'; +import { AnimationCurve } from '@nativescript/core/ui/enums'; +import { CustomTransition } from './custom-transition'; + +function _testTransition(navigationTransition: NavigationTransition) { + var testId = `Transition[${JSON.stringify(navigationTransition)}]`; + if (Trace.isEnabled()) { + Trace.write(`Testing ${testId}`, Trace.categories.Test); + } + var navigationEntry: NavigationEntry = { + create: function (): Page { + let page = new Page(); + page.id = testId; + page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return page; + }, + animated: true, + transition: navigationTransition, + }; + + helper.navigateWithEntry(navigationEntry); +} + +export function test_Transitions() { + helper.navigate(() => { + const page = new Page(); + page.id = 'TransitionsTestPage_MAIN'; + page.style.backgroundColor = new Color(255, Math.round(Math.random() * 255), Math.round(Math.random() * 255), Math.round(Math.random() * 255)); + + return page; + }); + + var transitions; + if (platform.Device.os === platform.platformNames.ios) { + transitions = ['curl']; + } else { + const _sdkVersion = parseInt(platform.Device.sdkVersion); + transitions = _sdkVersion >= 21 ? ['explode'] : []; + } + + transitions = transitions.concat(['fade', 'slide']); + + // Custom transition + _testTransition({ instance: new CustomTransition(), duration: 10 }); + + // Built-in transitions + transitions.forEach((name) => { + _testTransition({ name, duration: 20, curve: AnimationCurve.easeIn }); + }); + + // helper.navigateWithEntry({ create: mainPageFactory, clearHistory: true, animated: false }); +} diff --git a/packages/core/__tests__/e2e/automated/app/package.json b/apps/automated/app/package.json similarity index 100% rename from packages/core/__tests__/e2e/automated/app/package.json rename to apps/automated/app/package.json diff --git a/apps/automated/app/pages/background-test.ts b/apps/automated/app/pages/background-test.ts new file mode 100644 index 000000000..47fb5d1af --- /dev/null +++ b/apps/automated/app/pages/background-test.ts @@ -0,0 +1,13 @@ +import * as view from '@nativescript/core/ui/core/view'; +import * as pages from '@nativescript/core/ui/page'; + +export function applyTap(args) { + var page = (args.object).page; + var css = '#test-element { ' + args.object.tag + ' }'; + page.css = css; +} + +export function resetTap(args) { + var page = (args.object).page; + page.css = ''; +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/background-test.xml b/apps/automated/app/pages/background-test.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/background-test.xml rename to apps/automated/app/pages/background-test.xml diff --git a/apps/automated/app/pages/file-load-test.ts b/apps/automated/app/pages/file-load-test.ts new file mode 100644 index 000000000..b349603e3 --- /dev/null +++ b/apps/automated/app/pages/file-load-test.ts @@ -0,0 +1,28 @@ +import { Page, Label, knownFolders, path, ModuleNameResolver } from '@nativescript/core'; + +export function createPage() { + var page = new Page(); + var lbl = new Label(); + + var moduleName = 'tests/pages/files/test'; + + ModuleNameResolver; + var resolver = new ModuleNameResolver({ + width: 400, + height: 600, + os: 'android', + deviceType: 'phone', + }); + + // Current app full path. + var currentAppPath = knownFolders.currentApp().path; + var moduleNamePath = path.join(currentAppPath, moduleName); + + var fileName = resolver.resolveModuleName(moduleNamePath, 'xml'); + lbl.text = fileName; + lbl.textWrap = true; + + page.content = lbl; + + return page; +} diff --git a/apps/automated/app/pages/fonts-test.ts b/apps/automated/app/pages/fonts-test.ts new file mode 100644 index 000000000..1ed2aff3c --- /dev/null +++ b/apps/automated/app/pages/fonts-test.ts @@ -0,0 +1,17 @@ +import * as stack from '@nativescript/core/ui/layouts/stack-layout'; +import { unsetValue } from '@nativescript/core'; + +export function buttonTap(args) { + var stackLayout = args.object.parent; + + for (var i = 0; i < stackLayout.getChildrenCount(); i++) { + var v = stackLayout.getChildAt(i); + v.style.fontFamily = unsetValue; + v.style.fontSize = unsetValue; + v.style.fontStyle = unsetValue; + v.style.fontWeight = unsetValue; + + v.style.color = unsetValue; + v.style.textAlignment = unsetValue; + } +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/fonts-test.xml b/apps/automated/app/pages/fonts-test.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/fonts-test.xml rename to apps/automated/app/pages/fonts-test.xml diff --git a/packages/core/__tests__/e2e/automated/app/pages/package.json b/apps/automated/app/pages/package.json similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/package.json rename to apps/automated/app/pages/package.json diff --git a/apps/automated/app/pages/page10.ts b/apps/automated/app/pages/page10.ts new file mode 100644 index 000000000..f32646d2c --- /dev/null +++ b/apps/automated/app/pages/page10.ts @@ -0,0 +1,47 @@ +import { Page } from '@nativescript/core/ui/page'; +import { ImageSource } from '@nativescript/core/image-source'; +import { GridLayout, ItemSpec } from '@nativescript/core/ui/layouts/grid-layout'; +import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; +import { Label } from '@nativescript/core/ui/label'; +import { Image } from '@nativescript/core/ui/image'; + +export function createPage() { + var stack = new StackLayout(); + var grid = new GridLayout(); + stack.addChild(grid); + + grid.addColumn(new ItemSpec(80, 'pixel')); + grid.addColumn(new ItemSpec(1, 'star')); + grid.addRow(new ItemSpec(1, 'auto')); + grid.addRow(new ItemSpec(1, 'auto')); + + var defaultImageSource = ImageSource.fromFileSync(__dirname + '/test.png'); + + var img = new Image(); + img.src = defaultImageSource; + + img.width = 80; + img.height = 80; + img.verticalAlignment = 'bottom'; + GridLayout.setRowSpan(img, 2); + grid.addChild(img); + + var titleLabel = new Label(); + titleLabel.textWrap = true; + titleLabel.text = 'some text goes here'; + GridLayout.setColumn(titleLabel, 1); + grid.addChild(titleLabel); + + var commentsLabel = new Label(); + commentsLabel.text = 'comments'; + commentsLabel.verticalAlignment = 'bottom'; + GridLayout.setRow(commentsLabel, 1); + GridLayout.setColumn(commentsLabel, 1); + grid.addChild(commentsLabel); + + var page = new Page(); + page.content = stack; + page.css = 'GridLayout { background-color: yellow } image { background-color: green } label { background-color: red } stackpnael { background-color: pink }'; + + return page; +} diff --git a/apps/automated/app/pages/page11.ts b/apps/automated/app/pages/page11.ts new file mode 100644 index 000000000..624b57a26 --- /dev/null +++ b/apps/automated/app/pages/page11.ts @@ -0,0 +1,74 @@ +import * as gridModule from '@nativescript/core/ui/layouts/grid-layout'; +import * as sp from '@nativescript/core/ui/layouts/stack-layout'; +import * as button from '@nativescript/core/ui/button'; +import { Page } from '@nativescript/core/ui/page'; + +export function createPage() { + var StackLayout = new sp.StackLayout(); + var grid = new gridModule.GridLayout(); + grid.horizontalAlignment = 'left'; + + StackLayout.addChild(grid); + + var btn1 = new button.Button(); + btn1.text = 'btn1'; + var btn2 = new button.Button(); + btn2.text = 'btn2'; + var btn3 = new button.Button(); + btn3.text = 'btn3'; + var btn4 = new button.Button(); + btn4.text = 'btn4'; + + grid.addChild(btn2); + grid.addChild(btn3); + grid.addChild(btn4); + + var sp1 = new sp.StackLayout(); + sp1.orientation = 'horizontal'; + sp1.height = 200; + + var b1 = new button.Button(); + b1.text = 'nested Btn1'; + sp1.addChild(b1); + + var b2 = new button.Button(); + b2.text = 'nested Btn2'; + sp1.addChild(b2); + + grid.addChild(sp1); + + gridModule.GridLayout.setColumn(btn4, 1); + gridModule.GridLayout.setColumn(btn3, 1); + gridModule.GridLayout.setRow(btn2, 1); + gridModule.GridLayout.setRow(btn4, 1); + + grid.addRow(new gridModule.ItemSpec()); + grid.addRow(new gridModule.ItemSpec()); + grid.addColumn(new gridModule.ItemSpec()); + grid.addColumn(new gridModule.ItemSpec()); + + var page = new Page(); + //page.content = GridLayout; + page.content = StackLayout; + var x = 1; + btn1.on(button.Button.tapEvent, function () { + x++; + var gravity; + //btn1.android.setLayoutParams(new android.view.ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT)); + if (x === 1) { + gravity = android.view.Gravity.CENTER; + } else if (x === 2) { + gravity = android.view.Gravity.RIGHT | android.view.Gravity.BOTTOM; + } else { + gravity = android.view.Gravity.LEFT | android.view.Gravity.TOP; + x = 0; + } + + for (var i = 0; i < grid.getChildrenCount(); i++) { + grid.getChildAt(i).android.setGravity(gravity); + } + }); + + return page; +} +//export var Page = page; diff --git a/apps/automated/app/pages/page12.ts b/apps/automated/app/pages/page12.ts new file mode 100644 index 000000000..ba4dcaf9c --- /dev/null +++ b/apps/automated/app/pages/page12.ts @@ -0,0 +1,54 @@ +import * as pages from '@nativescript/core/ui/page'; +import * as btns from '@nativescript/core/ui/button'; +import * as tb from '@nativescript/core/ui/text-field'; +import * as gridLayoutModule from '@nativescript/core/ui/layouts/grid-layout'; + +export function createPage() { + var page = new pages.Page(); + var gridLayout = new gridLayoutModule.GridLayout(); + + var lengths = [new gridLayoutModule.ItemSpec(140, 'pixel'), new gridLayoutModule.ItemSpec(1, 'star'), new gridLayoutModule.ItemSpec(140, 'pixel')]; + + var rows = 2; + var cols = 3; + var row; + var col; + + for (row = 0; row < rows; row++) { + var rowDef = new gridLayoutModule.ItemSpec(1, 'auto'); + gridLayout.addRow(rowDef); + } + + for (col = 0; col < cols; col++) { + gridLayout.addColumn(lengths[col]); + } + + var btn = new btns.Button(); + btn.text = 'Col: 0'; + gridLayoutModule.GridLayout.setColumn(btn, 0); + gridLayoutModule.GridLayout.setRow(btn, 0); + gridLayout.addChild(btn); + + var btn2 = new btns.Button(); + btn2.text = 'Col: 2'; + gridLayoutModule.GridLayout.setColumn(btn2, 2); + gridLayoutModule.GridLayout.setRow(btn2, 0); + gridLayout.addChild(btn2); + + var txt = new tb.TextField(); + txt.text = 'Col: 1'; + txt.width = 140; + gridLayoutModule.GridLayout.setColumn(txt, 1); + gridLayoutModule.GridLayout.setRow(txt, 0); + gridLayout.addChild(txt); + + var txt2 = new tb.TextField(); + txt2.text = 'Col: All'; + gridLayoutModule.GridLayout.setColumnSpan(txt2, 3); + gridLayoutModule.GridLayout.setRow(txt2, 1); + gridLayout.addChild(txt2); + + page.content = gridLayout; + + return page; +} diff --git a/apps/automated/app/pages/page13.ts b/apps/automated/app/pages/page13.ts new file mode 100644 index 000000000..31f73388a --- /dev/null +++ b/apps/automated/app/pages/page13.ts @@ -0,0 +1,35 @@ +import * as pages from '@nativescript/core/ui/page'; +import * as btns from '@nativescript/core/ui/button'; +import * as layout from '@nativescript/core/ui/layouts/stack-layout'; + +export function createPage() { + var page = new pages.Page(); + var linearLayout = new layout.StackLayout(); + + var btn = addButton(linearLayout, 'left'); + btn.marginLeft = 100; + btn = addButton(linearLayout, 'center'); + btn.marginTop = 100; + btn = addButton(linearLayout, 'right'); + btn.marginRight = 100; + + btn = addButton(linearLayout, 'stretch'); + btn.marginLeft = 100; + btn.marginRight = 100; + btn.marginTop = 100; + btn.marginBottom = 100; + + page.content = linearLayout; + + return page; +} + +function addButton(layout: layout.StackLayout, text: 'left' | 'center' | 'right' | 'stretch') { + var btn = new btns.Button(); + btn.text = text; + btn.horizontalAlignment = text; + layout.addChild(btn); + layout.style.paddingLeft = 5; + + return btn; +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/page14.xml b/apps/automated/app/pages/page14.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/page14.xml rename to apps/automated/app/pages/page14.xml diff --git a/apps/automated/app/pages/page15.ts b/apps/automated/app/pages/page15.ts new file mode 100644 index 000000000..742590b21 --- /dev/null +++ b/apps/automated/app/pages/page15.ts @@ -0,0 +1,40 @@ +import * as pageModule from '@nativescript/core/ui/page'; +import * as buttonModule from '@nativescript/core/ui/button'; +import * as stackModule from '@nativescript/core/ui/layouts/stack-layout'; + +export function createPage() { + var page = new pageModule.Page(); + var stackLayout = new stackModule.StackLayout(); + var btn1 = new buttonModule.Button(); + btn1.horizontalAlignment = 'left'; + btn1.verticalAlignment = 'top'; + btn1.marginTop = 10; + btn1.marginRight = 0; + btn1.marginBottom = 10; + btn1.marginLeft = 20; + btn1.text = 'top, left'; + + var btn2 = new buttonModule.Button(); + btn2.horizontalAlignment = 'center'; + btn2.verticalAlignment = 'middle'; + btn2.text = 'center, center'; + + var btn3 = new buttonModule.Button(); + btn3.horizontalAlignment = 'right'; + btn3.verticalAlignment = 'bottom'; + btn3.text = 'bottom, right'; + + var btn4 = new buttonModule.Button(); + btn4.horizontalAlignment = 'stretch'; + btn4.verticalAlignment = 'stretch'; + btn4.text = 'stretch, stretch'; + + stackLayout.addChild(btn1); + stackLayout.addChild(btn2); + stackLayout.addChild(btn3); + stackLayout.addChild(btn4); + + page.content = stackLayout; + + return page; +} diff --git a/apps/automated/app/pages/page16.ts b/apps/automated/app/pages/page16.ts new file mode 100644 index 000000000..66d01e3df --- /dev/null +++ b/apps/automated/app/pages/page16.ts @@ -0,0 +1,56 @@ +import * as pageModule from '@nativescript/core/ui/page'; +import * as buttonModule from '@nativescript/core/ui/button'; +import * as stackModule from '@nativescript/core/ui/layouts/stack-layout'; +import { Frame } from '@nativescript/core/ui/frame'; + +export function createPage() { + var page = new pageModule.Page(); + + //var iconItem = new pageModule.MenuItem(); + //iconItem.text = "TEST"; + + //iconItem.icon = "~/app" + "/tests" + "/test-icon.png"; // use + to stop regex replace during build + //iconItem.on("tap", () => { + // console.log("Icon item tapped"); + //}); + //page.optionsMenu.addItem(iconItem); + + //var textItem = new pageModule.MenuItem(); + //textItem.text = "SAVE"; + //textItem.on("tap", () => { + // console.log("Save item tapped"); + //}); + //page.optionsMenu.addItem(textItem); + + var stackLayout = new stackModule.StackLayout(); + //var count = 0; + var btn1 = new buttonModule.Button(); + btn1.text = 'add item'; + //btn1.on("tap", () => { + // console.log("adding menu item"); + + // var newItem = new pageModule.MenuItem(); + // var text = "item " + count; + // newItem.text = text + // newItem.on("tap", () => { + // console.log("ITEM [" + text + "] tapped"); + // }); + // page.optionsMenu.addItem(newItem); + // count++; + //}); + + stackLayout.addChild(btn1); + + var btn2 = new buttonModule.Button(); + btn2.text = 'navigate'; + btn2.on('tap', () => { + var nextPage = 'app/tests/pages/page16'; + Frame.topmost().navigate(nextPage); + }); + + stackLayout.addChild(btn2); + + page.content = stackLayout; + + return page; +} diff --git a/apps/automated/app/pages/page17.ts b/apps/automated/app/pages/page17.ts new file mode 100644 index 000000000..6ca5785a6 --- /dev/null +++ b/apps/automated/app/pages/page17.ts @@ -0,0 +1,36 @@ +import * as observable from '@nativescript/core/data/observable'; +import * as action from '@nativescript/core/ui/action-bar'; + +import * as pages from '@nativescript/core/ui/page'; + +var currentPage: pages.Page; +// Event handler for Page "loaded" event attached in main-page.xml +export function pageLoaded(args: observable.EventData) { + // Get the event sender + var page = args.object; + currentPage = page; + var textItem = new action.ActionItem(); + textItem.text = 'from loaded'; + textItem.on('tap', () => { + console.log('item added in page.loaded tapped!!!'); + }); + page.actionBar.actionItems.addItem(textItem); +} + +export function optionTap(args) { + console.log('item added form XML tapped!!!'); +} +var i = 0; +export function buttonTap(args: observable.EventData) { + currentPage.actionBar.title = 'hi ' + i++; + + if (currentPage.actionBar.android) { + if (i % 3 === 0) { + currentPage.actionBar.android.icon = 'res://ic_test'; + } else if (i % 3 === 1) { + currentPage.actionBar.android.icon = '~/assets/test-icon.png'; + } else if (i % 3 === 2) { + currentPage.actionBar.android.icon = undefined; + } + } +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/page17.xml b/apps/automated/app/pages/page17.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/page17.xml rename to apps/automated/app/pages/page17.xml diff --git a/apps/automated/app/pages/page18.ts b/apps/automated/app/pages/page18.ts new file mode 100644 index 000000000..9bb9f3795 --- /dev/null +++ b/apps/automated/app/pages/page18.ts @@ -0,0 +1,22 @@ +import { Frame } from '@nativescript/core/ui/frame'; +import * as observable from '@nativescript/core/data/observable'; + +import { Trace } from '@nativescript/core'; +Trace.setCategories('gestures'); +Trace.enable(); + +export function itemTap(args) { + console.log('----- Item tapped: ' + args.view.tag); + + Frame.topmost().navigate({ + moduleName: './pages/page5', + }); +} + +export function itemLoaded(args: observable.EventData) { + console.log('----- Item loaded: ' + (args.object).tag); +} + +export function itemUnloaded(args: observable.EventData) { + console.log('----- Item unloaded: ' + (args.object).tag); +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/page18.xml b/apps/automated/app/pages/page18.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/page18.xml rename to apps/automated/app/pages/page18.xml diff --git a/apps/automated/app/pages/page19.ts b/apps/automated/app/pages/page19.ts new file mode 100644 index 000000000..dbd60eea8 --- /dev/null +++ b/apps/automated/app/pages/page19.ts @@ -0,0 +1,8 @@ +import * as observable from '@nativescript/core/data/observable'; +import { Trace } from '@nativescript/core'; +Trace.addCategories(Trace.categories.Layout); +Trace.enable(); + +export function onLoaded(args: observable.EventData) { + (args.object).bindingContext = [0, 1]; +} diff --git a/packages/core/__tests__/e2e/automated/app/pages/page19.xml b/apps/automated/app/pages/page19.xml similarity index 100% rename from packages/core/__tests__/e2e/automated/app/pages/page19.xml rename to apps/automated/app/pages/page19.xml diff --git a/apps/automated/app/pages/page20.ts b/apps/automated/app/pages/page20.ts new file mode 100644 index 000000000..9e8a09976 --- /dev/null +++ b/apps/automated/app/pages/page20.ts @@ -0,0 +1,11 @@ +import * as observable from '@nativescript/core/data/observable'; +import { Trace } from '@nativescript/core'; +import { Button } from '@nativescript/core/ui/button'; +import { Page } from '@nativescript/core/ui/page'; +Trace.addCategories(Trace.categories.Layout); +Trace.enable(); + +export function onTap(args: observable.EventData) { + var btn = + + + `; + + this.executeSnippet( + this.getViews(snippet), + this.noop, + ({ root, child0, child1 }) => { + const insets = root.getSafeAreaInsets(); + equal(left(child0), insets.left, `${child0}.left - actual: ${left(child0)} expected: ${insets.left}`); + equal(top(child0), insets.top, `${child0}.top - actual: ${top(child0)} expected: ${insets.top}`); + equal(right(child0), width(root) - insets.right, `${child0}.right - actual: ${right(child0)} expected: ${width(root) - insets.right}`); + equal(bottom(child0), height(root) - insets.bottom, `${child0}.bottom - actual: ${bottom(child0)} expected: ${height(root) - insets.bottom}`); + equal(height(child1), 0, `${child1} has been laid out, but should not`); + equal(width(child1), 0, `${child1} has been laid out, but should not`); + }, + pageOptions + ); + } + + public test_wrap_horizontal_children_components_in_safe_area_action_bar() { + this.wrap_horizontal_children_components_in_safe_area({ actionBar: true }); + } + + public test_wrap_horizontal_children_components_in_safe_area_action_bar_hidden() { + this.wrap_horizontal_children_components_in_safe_area({ actionBarHidden: true }); + } + + public test_wrap_horizontal_children_components_in_safe_area_tab_bar() { + this.wrap_horizontal_children_components_in_safe_area({ tabBar: true }); + } + + private wrap_vertical_children_components_in_safe_area(pageOptions?: helper.PageOptions) { + const snippet = ` + + + + + `; + + this.executeSnippet( + this.getViews(snippet), + this.noop, + ({ root, child0, child1 }) => { + const insets = root.getSafeAreaInsets(); + equal(left(child0), insets.left, `${child0}.left - actual: ${left(child0)} expected: ${insets.left}`); + equal(top(child0), insets.top, `${child0}.top - actual: ${top(child0)} expected: ${insets.top}`); + equal(right(child0), width(root) - insets.right, `${child0}.right - actual: ${right(child0)} expected: ${width(root) - insets.right}`); + equal(bottom(child0), height(root) - insets.bottom, `${child0}.bottom - actual: ${bottom(child0)} expected: ${height(root) - insets.bottom}`); + equal(height(child1), 0, `${child1} has been laid out, but should not`); + equal(width(child1), 0, `${child1} has been laid out, but should not`); + }, + pageOptions + ); + } + + public test_wrap_vertical_children_components_in_safe_area_action_bar() { + this.wrap_vertical_children_components_in_safe_area({ actionBar: true }); + } + + public test_wrap_vertical_children_components_in_safe_area_action_bar_hidden() { + this.wrap_vertical_children_components_in_safe_area({ actionBarHidden: true }); + } + + public test_wrap_vertical_children_components_in_safe_area_tab_bar() { + this.wrap_vertical_children_components_in_safe_area({ tabBar: true }); + } + + private wrap_nested_layouts_beyond_safe_area(pageOptions?: helper.PageOptions) { + const snippet = ` + + + '); + function testAction(views: Array) { + var page = views[0]; + var testButton = '); + function testAction(views: Array) { + var page = views[0]; + var testButton = '); + function testAction(views: Array) { + var page = views[0]; + var testButton = '); + function testAction(views: Array) { + var page = views[0]; + var testButton = - - - `; - - this.executeSnippet( - this.getViews(snippet), - this.noop, - ({ root, child0, child1 }) => { - const insets = root.getSafeAreaInsets(); - equal(left(child0), insets.left, `${child0}.left - actual: ${left(child0)} expected: ${insets.left}`); - equal(top(child0), insets.top, `${child0}.top - actual: ${top(child0)} expected: ${insets.top}`); - equal(right(child0), width(root) - insets.right, `${child0}.right - actual: ${right(child0)} expected: ${width(root) - insets.right}`); - equal(bottom(child0), height(root) - insets.bottom, `${child0}.bottom - actual: ${bottom(child0)} expected: ${height(root) - insets.bottom}`); - equal(height(child1), 0, `${child1} has been laid out, but should not`); - equal(width(child1), 0, `${child1} has been laid out, but should not`); - }, - pageOptions - ); - } - - public test_wrap_horizontal_children_components_in_safe_area_action_bar() { - this.wrap_horizontal_children_components_in_safe_area({ actionBar: true }); - } - - public test_wrap_horizontal_children_components_in_safe_area_action_bar_hidden() { - this.wrap_horizontal_children_components_in_safe_area({ actionBarHidden: true }); - } - - public test_wrap_horizontal_children_components_in_safe_area_tab_bar() { - this.wrap_horizontal_children_components_in_safe_area({ tabBar: true }); - } - - private wrap_vertical_children_components_in_safe_area(pageOptions?: helper.PageOptions) { - const snippet = ` - - - - - `; - - this.executeSnippet( - this.getViews(snippet), - this.noop, - ({ root, child0, child1 }) => { - const insets = root.getSafeAreaInsets(); - equal(left(child0), insets.left, `${child0}.left - actual: ${left(child0)} expected: ${insets.left}`); - equal(top(child0), insets.top, `${child0}.top - actual: ${top(child0)} expected: ${insets.top}`); - equal(right(child0), width(root) - insets.right, `${child0}.right - actual: ${right(child0)} expected: ${width(root) - insets.right}`); - equal(bottom(child0), height(root) - insets.bottom, `${child0}.bottom - actual: ${bottom(child0)} expected: ${height(root) - insets.bottom}`); - equal(height(child1), 0, `${child1} has been laid out, but should not`); - equal(width(child1), 0, `${child1} has been laid out, but should not`); - }, - pageOptions - ); - } - - public test_wrap_vertical_children_components_in_safe_area_action_bar() { - this.wrap_vertical_children_components_in_safe_area({ actionBar: true }); - } - - public test_wrap_vertical_children_components_in_safe_area_action_bar_hidden() { - this.wrap_vertical_children_components_in_safe_area({ actionBarHidden: true }); - } - - public test_wrap_vertical_children_components_in_safe_area_tab_bar() { - this.wrap_vertical_children_components_in_safe_area({ tabBar: true }); - } - - private wrap_nested_layouts_beyond_safe_area(pageOptions?: helper.PageOptions) { - const snippet = ` - - - "); - function testAction(views: Array) { - var page = views[0]; - var testButton = "); - function testAction(views: Array) { - var page = views[0]; - var testButton = "); - function testAction(views: Array) { - var page = views[0]; - var testButton = "); - function testAction(views: Array) { - var page = views[0]; - var testButton =