import * as frame from "tns-core-modules/ui/frame"; import { ViewBase, View, unsetValue, isIOS } from "tns-core-modules/ui/core/view"; import { Page } from "tns-core-modules/ui/page"; import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout"; import { Button } from "tns-core-modules/ui/button"; import * as TKUnit from "../TKUnit"; import * as utils from "tns-core-modules/utils/utils"; import { ActionBar } from "tns-core-modules/ui/action-bar"; import { Color } from "tns-core-modules/color"; import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base"; import { FlexboxLayout } from "tns-core-modules/ui/layouts/flexbox-layout"; import { FormattedString, Span } from "tns-core-modules/text/formatted-string"; import { _getProperties, _getStyleProperties } from "tns-core-modules/ui/core/properties"; import { device } from "tns-core-modules/platform"; // TODO: Remove this and get it from global to decouple builder for angular import { createViewFromEntry } from "tns-core-modules/ui/builder"; const DELTA = 0.1; const sdkVersion = parseInt(device.sdkVersion); export var ASYNC = 0.2; export var MEMORY_ASYNC = 2; export function getColor(uiColor: UIColor): Color { var redRef = new interop.Reference(); var greenRef = new interop.Reference(); var blueRef = new interop.Reference(); var alphaRef = new interop.Reference(); uiColor.getRedGreenBlueAlpha(redRef, greenRef, blueRef, alphaRef); var red = redRef.value * 255; var green = greenRef.value * 255; var blue = blueRef.value * 255; var alpha = alphaRef.value * 255; return new Color(alpha, red, green, blue); } export function clearPage(): void { let newPage = getCurrentPage(); if (!newPage) { throw new Error("NO CURRENT PAGE!!!!"); } newPage.style.backgroundColor = unsetValue; newPage.style.color = unsetValue; newPage.bindingContext = unsetValue; newPage.className = unsetValue; newPage.id = unsetValue; } export function do_PageTest(test: (views: [Page, View, View, View, ActionBar]) => void, content: View, secondView: View, thirdView: View) { clearPage(); let newPage = getCurrentPage(); newPage.content = content; test([newPage, content, secondView, thirdView, newPage.actionBar]); newPage.content = null; } export function do_PageTest_WithButton(test: (views: [Page, Button, ActionBar]) => void) { clearPage(); let newPage = getCurrentPage(); let btn = new Button(); newPage.content = btn; test([newPage, btn, newPage.actionBar]); newPage.content = null; } export function do_PageTest_WithStackLayout_AndButton(test: (views: [Page, StackLayout, Button, ActionBar]) => void) { clearPage(); let newPage = getCurrentPage(); let stackLayout = new StackLayout(); let btn = new Button(); stackLayout.addChild(btn); newPage.content = stackLayout; test([newPage, stackLayout, btn, newPage.actionBar]); newPage.content = null; } //export function buildUIAndRunTest(controlToTest, testFunction, pageCss?, testDelay?) { export function buildUIAndRunTest(controlToTest: T, testFunction: (views: [T, Page]) => void, pageCss?) { clearPage(); let newPage = getCurrentPage(); newPage.css = pageCss; newPage.content = controlToTest; testFunction([controlToTest, newPage]); newPage.content = null; newPage.css = null; } export function buildUIWithWeakRefAndInteract(createFunc: () => T, interactWithViewFunc?: (view: T) => void, done?) { clearPage(); const page = getCurrentPage(); const weakRef = new WeakRef(createFunc()); page.content = weakRef.get(); if (interactWithViewFunc) { interactWithViewFunc(weakRef.get()); } page.content = null; // Give a change for native cleanup (e.g. keyboard close, etc.). TKUnit.wait(0.001); if (page.ios) { /* tslint:disable:no-unused-expression */ // Could cause GC on the next call. // NOTE: Don't replace this with forceGC(); new ArrayBuffer(4 * 1024 * 1024); } utils.GC(); try { TKUnit.assert(!weakRef.get(), weakRef.get() + " leaked!"); done(null); } catch (ex) { done(ex); } } export function navigateToModuleAndRunTest(moduleName, context, testFunction) { let page = navigateToModule(moduleName, context); testFunction(page); } export function navigate(pageFactory: () => Page, navigationContext?: any): Page { let entry: frame.NavigationEntry = { create: pageFactory, animated: false, context: navigationContext, clearHistory: true }; return navigateWithEntry(entry); } export function navigateWithHistory(pageFactory: () => Page, navigationContext?: any): Page { let entry: frame.NavigationEntry = { create: pageFactory, animated: false, context: navigationContext, clearHistory: false }; return navigateWithEntry(entry); } export function navigateToModule(moduleName: string, context?: any): Page { let entry: frame.NavigationEntry = { moduleName: moduleName, context: context, animated: false, clearHistory: true }; return navigateWithEntry(entry); } export function getCurrentPage(): Page { return frame.topmost().currentPage; } export function getClearCurrentPage(): Page { let page = frame.topmost().currentPage; page.style.backgroundColor = unsetValue; page.style.color = unsetValue; page.bindingContext = unsetValue; page.className = unsetValue; page.id = unsetValue; page.css = ""; return page; } export function waitUntilNavigatedTo(page: Page, action: Function) { let completed = false; function navigatedTo(args) { args.object.page.off("navigatedTo", navigatedTo); completed = true; } page.on("navigatedTo", navigatedTo); action(); TKUnit.waitUntilReady(() => completed, 5); } export function waitUntilNavigatedFrom(action: Function) { const currentPage = frame.topmost().currentPage; let completed = false; function navigatedFrom(args) { args.object.page.off("navigatedFrom", navigatedFrom); completed = true; } currentPage.on("navigatedFrom", navigatedFrom); action(); TKUnit.waitUntilReady(() => completed); } export function waitUntilLayoutReady(view: View): void { TKUnit.waitUntilReady(() => view.isLayoutValid); } export function navigateWithEntry(entry: frame.NavigationEntry): Page { const page = createViewFromEntry(entry) as Page; entry.moduleName = null; entry.create = function () { return page; }; waitUntilNavigatedFrom(() => frame.topmost().navigate(entry)); return page; } export function goBack() { waitUntilNavigatedFrom(() => frame.topmost().goBack()); } export function assertAreClose(actual: number, expected: number, message: string): void { const density = utils.layout.getDisplayDensity(); const delta = Math.floor(density) !== density ? 1.1 : DELTA; TKUnit.assertAreClose(actual, expected, delta, message); } export function assertViewColor(testView: View, hexColor: string) { TKUnit.assert(testView.style.color, "Color property not applied correctly. Style value is not defined."); TKUnit.assertEqual(testView.style.color.hex, hexColor, "color property"); } export function assertViewBackgroundColor(testView: ViewBase, hexColor: string) { TKUnit.assert(testView.style.backgroundColor, "Background color property not applied correctly. Style value is not defined."); TKUnit.assertEqual(testView.style.backgroundColor.hex, hexColor, "backgroundColor property"); } export function assertTabSelectedTabTextColor(testView: ViewBase, hexColor: string) { TKUnit.assert(testView.style.selectedTabTextColor, "selectedTabTextColor property not applied correctly. Style value is not defined."); TKUnit.assertEqual(testView.style.selectedTabTextColor.hex, hexColor, "selectedTabTextColor property not applied correctly"); } export function forceGC() { if (isIOS) { /* tslint:disable:no-unused-expression */ // Could cause GC on the next call. new ArrayBuffer(4 * 1024 * 1024); } utils.GC(); TKUnit.wait(0.001); } export function _generateFormattedString(): FormattedString { let formattedString = new FormattedString(); let span: Span; span = new Span(); span.fontFamily = "serif"; span.fontSize = 10; span.fontWeight = "bold"; span.color = new Color("red"); span.backgroundColor = new Color("blue"); span.textDecoration = "line-through"; span.text = "Formatted"; formattedString.spans.push(span); span = new Span(); span.fontFamily = "sans-serif"; span.fontSize = 20; span.fontStyle = "italic"; span.color = new Color("green"); span.backgroundColor = new Color("yellow"); span.textDecoration = "underline"; span.text = "Text"; formattedString.spans.push(span); return formattedString; } const props = _getProperties(); const styleProps = _getStyleProperties(); let setters: Map; let cssSetters: Map; let defaultNativeGetters: Map any>; export function nativeView_recycling_test(createNew: () => View, createLayout?: () => LayoutBase, nativeGetters?: Map any>, customSetters?: Map) { return; // if (isIOS) { // // recycling not implemented yet. // return; // } // setupSetters(); // const page = getClearCurrentPage(); // let layout: LayoutBase = new FlexboxLayout(); // if (createLayout) { // // This is done on purpose. We need the constructor of Flexbox // // to run otherwise some module fileds stays uninitialized. // layout = createLayout(); // } // page.content = layout; // const first = createNew(); // const test = createNew(); // // Make sure we are not reusing a native views. // first.recycleNativeView = "never"; // test.recycleNativeView = "never"; // page.content = layout; // layout.addChild(test); // setValue(test.style, cssSetters); // setValue(test, setters, customSetters); // // Needed so we can reset formattedText // test["secure"] = false; // const nativeView = test.nativeViewProtected; // // Mark so we reuse the native views. // test.recycleNativeView = "always"; // layout.removeChild(test); // const newer = createNew(); // newer.recycleNativeView = "always"; // layout.addChild(newer); // layout.addChild(first); // if (first.typeName !== "SearchBar") { // // There are way too many differences in native methods for search-bar. // // There are too many methods that just throw for newly created views in API lvl 19 and 17 // if (sdkVersion < 21) { // TKUnit.waitUntilReady(() => layout.isLayoutValid); // } // compareUsingReflection(newer, first); // } // TKUnit.assertEqual(newer.nativeViewProtected, nativeView, "nativeView not reused."); // checkDefaults(newer, first, props, nativeGetters || defaultNativeGetters); // checkDefaults(newer, first, styleProps, nativeGetters || defaultNativeGetters); // layout.removeChild(newer); // layout.removeChild(first); } function compareUsingReflection(recycledNativeView: View, newNativeView: View): void { const recycled: android.view.View = recycledNativeView.nativeViewProtected; const newer: android.view.View = newNativeView.nativeViewProtected; TKUnit.assertNotEqual(recycled, newer); const methods = newer.getClass().getMethods(); for (let i = 0, length = methods.length; i < length; i++) { const method = methods[i]; const returnType = method.getReturnType(); const name = method.getName(); const skip = name.includes("ViewId") || name.includes("Accessibility") || name.includes("hashCode") || name === "getId" || name === "hasFocus" || name === "isDirty" || name === "getLeft" || name === "getTop" || name === "getRight" || name === "getBottom" || name === "getWidth" || name === "getHeight" || name === "getX" || name === "getY" || name.includes("getMeasured") || name === "toString"; if (skip || method.getParameterTypes().length > 0) { continue; } if ((java).lang.Comparable.class.isAssignableFrom(returnType)) { const defValue = method.invoke(newer, null); const currValue = method.invoke(recycled, null); if (defValue !== currValue && defValue.compareTo(currValue) !== 0) { throw new Error(`Actual: ${currValue}, Expected: ${defValue}, for method: ${method.getName()}`); } } else if (java.lang.String.class === returnType || java.lang.Character.class === returnType || (java).lang.CharSequence.class === returnType || returnType === java.lang.Byte.TYPE || returnType === java.lang.Double.TYPE || returnType === java.lang.Float.TYPE || returnType === java.lang.Integer.TYPE || returnType === java.lang.Long.TYPE || returnType === java.lang.Short.TYPE || returnType === java.lang.Boolean.TYPE) { const defValue = method.invoke(newer, null); const currValue = method.invoke(recycled, null); if ((currValue + "") !== (defValue + "")) { throw new Error(`Actual: ${currValue}, Expected: ${defValue}, for method: ${method.getName()}`); } } } } function checkDefaults(newer: View, first: View, props: Array, nativeGetters: Map any>): void { props.forEach(prop => { const name = (prop).name; if (nativeGetters.has(name)) { const getter = nativeGetters.get(name); TKUnit.assertDeepEqual(getter(newer), getter(first), name); } else if (newer[prop.getDefault]) { TKUnit.assertDeepEqual(newer[prop.getDefault](), first[prop.getDefault](), name); } else if (newer[prop.setNative]) { console.log(`Type: ${newer.typeName} has no getter for ${name} property.`) } }); } function setValue(object: Object, setters: Map, customSetters?: Map): void { setters.forEach((value1, key) => { let value = customSetters && customSetters.has(key) ? customSetters.get(key) : value1; const currentValue = object[key]; if (currentValue === value) { if (value === "horizontal" && key === "orientation") { // wrap-layout.orientation default value is 'horizontal' value = "vertical"; } else if (value === 2) { value = 3; } } object[key] = value; const newValue = object[key]; TKUnit.assertNotEqual(newValue, currentValue, `${object} setting ${key} should change current value.`); }); } function setupSetters(): void { if (setters) { return; } setters = new Map(); // view-base setters.set("id", "someId"); setters.set("className", "someClassName"); setters.set("bindingContext", "someBindingContext"); // view setters.set("automationText", "automationText"); setters.set("originX", 0.2); setters.set("originY", 0.2); setters.set("isEnabled", false); setters.set("isUserInteractionEnabled", false); // action-bar setters.set("title", "title"); setters.set("text", "text"); setters.set("icon", "~/logo.png"); setters.set("visibility", "collapse"); // activity-indicator setters.set("busy", true); // date-picker setters.set("year", "2010"); setters.set("month", "2"); setters.set("day", "2"); setters.set("maxDate", "2100"); setters.set("minDate", "2000"); setters.set("date", new Date(2011, 3, 3)); // editable-text setters.set("keyboardType", "datetime"); setters.set("returnKeyType", "done"); setters.set("editable", false); setters.set("updateTextTrigger", "focusLost"); setters.set("autocapitalizationType", "words"); setters.set("autocorrect", true); setters.set("hint", "hint"); setters.set("maxLength", "10"); // html-view setters.set("html", ""); // image-view setters.set("imageSource", ""); setters.set("src", ""); setters.set("loadMode", "async"); setters.set("isLoading", true); setters.set("stretch", "none"); // layout-base setters.set("clipToBounds", false); // absolute-layout setters.set("left", "20"); setters.set("top", "20"); // dock-layout setters.set("dock", "top"); setters.set("stretchLastChild", false); // grid-layout props setters.set("row", "1"); setters.set("rowSpan", "2"); setters.set("col", "1"); setters.set("colSpan", "2"); // stack-layout setters.set("orientation", "horizontal"); // wrap-layout // custom orientation value // setters.set('orientation', 'vertical'); setters.set("itemWidth", "50"); setters.set("itemHeight", "50"); // list-picker setters.set("items", ["1", "2", "3"]); setters.set("selectedIndex", "1"); // list-view setters.set("items", ["1", "2", "3"]); setters.set("itemTemplate", "