This commit is contained in:
Vasil Chimev
2019-11-20 18:26:45 +02:00
parent 0a4e28f5ae
commit 9b78a28f37
20 changed files with 1307 additions and 78 deletions

View File

@ -1,2 +1 @@
<Frame defaultPage="main-page">
</Frame>
<Frame defaultPage="home/home-page"></Frame>

View File

@ -1,25 +1,16 @@
/*
In NativeScript, the app.css file is where you place CSS rules that
you would like to apply to your entire application. Check out
http://docs.nativescript.org/ui/styling for a full list of the CSS
selectors and properties you can use to style UI components.
@import '~@nativescript/theme/css/core.css';
@import '~@nativescript/theme/css/default.css';
/*
In many cases you may want to use the NativeScript core theme instead
of writing your own CSS rules. You can learn more about the
NativeScript core theme at https://github.com/nativescript/theme
The imported CSS rules must precede all other types of rules.
*/
@import "~@nativescript/theme/css/core.css";
@import "~@nativescript/theme/css/default.css";
/* Place any CSS rules you want to apply on both iOS and Android here.
This is where the vast majority of your CSS code goes. */
/*
The following CSS rule changes the font size of all Buttons that have the
"-primary" class modifier.
*/
Button.-primary {
.btn {
font-size: 18;
}
.home-panel{
vertical-align: center;
font-size: 20;
margin: 15;
}
.description-label{
margin-bottom: 15;
}

63
e2e/appTestTs/app/home/data.ts Executable file
View File

@ -0,0 +1,63 @@
export function getData() {
return new Promise((resolve, reject) => {
const dataCopy = JSON.parse(JSON.stringify(data));
for (let i = 0; i < data.length; i++) {
const featured: boolean = i % 5 === 0;
(<any>dataCopy[i]).featured = featured;
const randomImageId = Math.floor(Math.random() * 5);
(<any>dataCopy[i]).image = "~/images/news" + randomImageId + ".jpg";
}
setTimeout(() => {
resolve(dataCopy);
}, 1000);
});
}
const data = [
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" },
{ "id": 1, "title": "Aliquam erat volutpat.", "date": "12/26/2018" },
{ "id": 2, "title": "Aenean fermentum.", "date": "5/8/2018" },
{ "id": 3, "title": "Vestibulum rutrum rutrum neque.", "date": "6/19/2018" }
];

View File

@ -0,0 +1,43 @@
.action-bar {
background-color: #12127D;
color: #BACECA;
}
.list-group {
background-color: #7A7878;
}
.list-item-featured {
background-color: #BACECA;
margin: 15 5 15 5;
}
.list-item-regular {
margin: 0 5 0 5;
padding: 5;
background-color: #BACECA;
}
.title-regular {
color: #142D63;
padding: 0 0 0 10;
}
.date-regular {
color: #4F77A5;
padding: 0 0 0 10;
}
.image-featured {
margin: 20 0 0 0;
}
.title-featured {
color: #142D63;
padding: 5 10 5 10;
}
.date-featured {
color: #4F77A5;
padding: 5 10 5 10;
}

View File

@ -0,0 +1,87 @@
import { EventData } from "tns-core-modules/data/observable";
import { Page, View } from "tns-core-modules/ui/page";
import { HomeViewModel } from "./home-view-model";
import { ListView, ItemEventData } from "tns-core-modules/ui/list-view/list-view";
import * as TKUnit from "./tk-unit";
import { Frame } from "@nativescript/core/ui/frame/frame";
export function onPageLoaded(args: EventData) {
console.log("---> Page Loaded!");
// let page = <Page>args.object;
// let listView = <ListView>page.getViewById("list-view");
// let indexes = {};
// let completed = false;
// listView.on(ListView.itemLoadingEvent, function (args: ItemEventData) {
// if (!args.view) {
// // args.view = new Label();
// console.log("---> Fail!!!");
// }
// // (<Label>args.view).text = "item " + args.index;
// indexes[args.index] = indexes[args.index] ? indexes[args.index] + 1 : 1;
// completed = args.index === (listView.items.length - 1);
// });
// let expected = 1;
// TKUnit.waitUntilReady(() => completed);
// console.log("---> Yeah!");
}
export function onItemLoading(args: EventData) {
console.log("--->", args.object);
}
export function onListViewLoaded(args: EventData) {
console.log("---> onListViewLoaded");
}
export function onTemplateLoaded(args: EventData) {
console.log("---> Template Loaded!");
}
export function onGridLoaded(args: EventData) {
console.log("---> Grid Loaded!");
}
export function navigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = new HomeViewModel();
}
export function selectItemTemplate(item, index, items) {
return item.featured ? "featured" : "regular";
}
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);
}
function waitUntilNavigatedFrom(action: Function, topFrame?: Frame) {
const currentPage = topFrame ? topFrame.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);
}

View File

@ -0,0 +1,28 @@
<Page navigatingTo="navigatingTo" class="page" xmlns="http://www.nativescript.org/tns.xsd" loaded="onPageLoaded">
<!-- <ActionBar title="News" class="action-bar">
</ActionBar> -->
<GridLayout>
<ListView id="list-view" class="list-group" items="{{ dataItems }}"
itemTemplateSelector="selectItemTemplate" separatorColor="transparent"
loadMoreItems="{{ onLoadMoreItems }}" loaded="onListViewLoaded" itemLoading="onItemLoading">
<ListView.itemTemplates>
<template key="regular">
<GridLayout>
<GridLayout rows="40, 30" columns="70, *" class="list-item-regular" loaded="onGridLoaded">
<Image row="0" col="0" rowSpan="2" src="{{ image }}"
stretch="aspectFill" />
<Label row="0" col="1" text="{{ title }}" class="h3 title-regular"
textWrap="true" height="40" />
<Label row="1" col="1" text="{{ date }}" class="h4 date-regular"
textWrap="true" height="30" />
</GridLayout>
</GridLayout>
</template>
</ListView.itemTemplates>
</ListView>
<ActivityIndicator busy="{{ isBusy }}" class="activity-indicator" />
</GridLayout>
</Page>

View File

@ -0,0 +1,27 @@
import { ObservableProperty } from "../observable-property-decorator";
import { ObservableArray } from "tns-core-modules/data/observable-array";
import { Observable } from "tns-core-modules/data/observable";
import { getData } from "./data";
export class HomeViewModel extends Observable {
@ObservableProperty() isBusy: boolean = true;
@ObservableProperty() dataItems: ObservableArray<any>;
constructor() {
super();
this.isBusy = true;
this.dataItems = new ObservableArray<any>();
getData().then((doctorsData) => {
this.dataItems.push(doctorsData);
this.isBusy = false;
});
}
onLoadMoreItems(args) {
getData().then((doctorsData) => {
this.dataItems.push(doctorsData);
});
}
}

View File

@ -0,0 +1,464 @@
/* tslint:disable */
/* Notes:
1. all test function names should begin with 'test'
2. (if exists) at the beginning of module test setUpModule() module function is called
3. (if exists) at the beginning of each test setUp() module function is called
4. tests should use TKUnit.assert(condition, message) to mark error. If no assert fails test is successful
5. (if exists) at the end of each test tearDown() module function is called
6. (if exists) at the end of module test tearDownModule() module function is called
*/
import * as application from "@nativescript/core/application";
import * as platform from "@nativescript/core/platform";
import * as timer from "@nativescript/core/timer";
import * as trace from "@nativescript/core/trace";
import * as types from "@nativescript/core/utils/types";
const sdkVersion = parseInt(platform.device.sdkVersion);
trace.enable();
export interface TestInfoEntry {
testFunc: () => void;
instance: Object;
isTest: boolean;
testName: string;
isPassed: boolean;
errorMessage: string;
testTimeout: number;
duration: number;
}
export function time(): number {
if (global.android) {
return java.lang.System.nanoTime() / 1000000; // 1 ms = 1000000 ns
} else {
return CACurrentMediaTime() * 1000;
}
}
export function write(message: string, type?: number) {
trace.write(message, trace.categories.Test, type);
}
function runTest(testInfo: TestInfoEntry) {
let start = time();
let duration;
try {
if (testInfo.instance) {
testInfo.testFunc.apply(testInfo.instance);
} else {
testInfo.testFunc();
}
if (testInfo.isTest) {
duration = time() - start;
testInfo.duration = duration;
write(`--- [${testInfo.testName}] OK, duration: ${duration.toFixed(2)}`, trace.messageType.info);
testInfo.isPassed = true;
}
}
catch (e) {
if (testInfo.isTest) {
duration = time() - start;
testInfo.duration = duration;
write(`--- [${testInfo.testName}] FAILED: ${e.message}, Stack: ${e.stack}, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.isPassed = false;
testInfo.errorMessage = e.message;
}
}
}
export interface TestFailure {
moduleName: string;
testName: string;
errorMessage: string;
}
export interface TestModuleRunResult {
name: string;
count: number;
succeeded: number;
failed: Array<TestFailure>;
}
let testsQueue: Array<TestInfoEntry>;
const defaultTimeout = 5000;
function runAsync(testInfo: TestInfoEntry, recursiveIndex: number, testTimeout?: number) {
let error;
let isDone = false;
let handle;
const testStartTime = time();
//write("--- [" + testInfo.testName + "] Started at: " + testStartTime, trace.messageType.info);
const doneCallback = (e: Error) => {
if (e) {
error = e;
} else {
isDone = true;
}
}
const timeout = testTimeout || testInfo.testTimeout || defaultTimeout;
let duration;
const checkFinished = () => {
duration = time() - testStartTime;
testInfo.duration = duration;
if (isDone) {
write(`--- [${testInfo.testName}] OK, duration: ${duration.toFixed(2)}`, trace.messageType.info);
testInfo.isPassed = true;
runTests(testsQueue, recursiveIndex + 1);
} else if (error) {
write(`--- [${testInfo.testName}] FAILED: ${error.message}, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = error.message;
runTests(testsQueue, recursiveIndex + 1);
} else {
const testEndTime = time();
if (testEndTime - testStartTime > timeout) {
write(`--- [${testInfo.testName}] TIMEOUT, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = "Test timeout.";
runTests(testsQueue, recursiveIndex + 1);
} else {
setTimeout(checkFinished, 20);
}
}
}
try {
if (testInfo.instance) {
testInfo.testFunc.apply(testInfo.instance, [doneCallback]);
} else {
const func: any = testInfo.testFunc;
func(doneCallback);
}
} catch (e) {
doneCallback(e);
}
setTimeout(checkFinished, 20);
}
export function runTests(tests: Array<TestInfoEntry>, recursiveIndex) {
testsQueue = tests;
for (let i = recursiveIndex; i < testsQueue.length; i++) {
const testEntry = testsQueue[i];
if (testEntry.testFunc.length > 0) {
return runAsync(testEntry, i);
} else {
runTest(testEntry);
}
}
}
export function assert(test: any, message?: string) {
if (!test) {
throw new Error(message);
}
}
export function assertTrue(test: boolean, message?: string) {
if (test !== true) {
throw new Error(message);
}
}
export function assertFalse(test: boolean, message?: string) {
if (test !== false) {
throw new Error(message);
}
}
export function assertNotEqual(actual: any, expected: any, message?: string) {
let equals = false;
if (types.isUndefined(actual) && types.isUndefined(expected)) {
equals = true;
} else if (!types.isNullOrUndefined(actual) && !types.isNullOrUndefined(expected)) {
if (types.isFunction(actual.equals)) {
// Use the equals method
if (actual.equals(expected)) {
equals = true;
}
} else {
equals = actual === expected;
}
}
if (equals) {
throw new Error(message + " Actual: " + actual + " Not_Expected: " + expected);
}
}
export function assertEqual<T extends { equals?(arg: T): boolean } | any>(actual: T, expected: T, message: string = '') {
if (!types.isNullOrUndefined(actual)
&& !types.isNullOrUndefined(expected)
&& types.getClass(actual) === types.getClass(expected)
&& types.isFunction(actual.equals)) {
// Use the equals method
if (!actual.equals(expected)) {
throw new Error(`${message} Actual: <${actual}>(${typeof (actual)}). Expected: <${expected}>(${typeof (expected)})`);
}
}
else if (actual !== expected) {
throw new Error(`${message} Actual: <${actual}>(${typeof (actual)}). Expected: <${expected}>(${typeof (expected)})`);
}
}
/**
* Assert two json like objects are deep equal.
*/
export function assertDeepEqual(actual, expected, message: string = '', path: any[] = []): void {
let typeofActual: string = typeof actual;
let typeofExpected: string = typeof expected;
if (typeofActual !== typeofExpected) {
throw new Error(message + ' ' + "At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
} else if (typeofActual === "object" || typeofActual === "array") {
if (expected instanceof Map) {
if (actual instanceof Map) {
expected.forEach((value, key) => {
if (actual.has(key)) {
assertDeepEqual(actual.get(key), value, message, path.concat([key]));
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected Map has key '" + key + "' but actual does not.");
}
});
actual.forEach((value, key) => {
if (!expected.has(key)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual Map has key '" + key + "' but expected does not.");
}
});
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected is Map but actual is not.");
}
}
if (expected instanceof Set) {
if (actual instanceof Set) {
expected.forEach(i => {
if (!actual.has(i)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected Set has item '" + i + "' but actual does not.");
}
});
actual.forEach(i => {
if (!expected.has(i)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual Set has item '" + i + "' but expected does not.");
}
})
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected is Set but actual is not.");
}
}
for (let key in actual) {
if (!(key in expected)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " found unexpected key " + key + ".");
}
assertDeepEqual(actual[key], expected[key], message, path.concat([key]));
}
for (let key in expected) {
if (!(key in actual)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected a key " + key + ".");
}
}
} else if (actual !== expected) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
}
}
export function assertDeepSuperset(actual, expected, path: any[] = []): void {
let typeofActual: string = typeof actual;
let typeofExpected: string = typeof expected;
if (typeofActual !== typeofExpected) {
throw new Error("At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
} else if (typeofActual === "object" || typeofActual === "array") {
for (let key in expected) {
if (!(key in actual)) {
throw new Error("At /" + path.join("/") + " expected a key " + key + ".");
}
assertDeepSuperset(actual[key], expected[key], path.concat([key]));
}
} else if (actual !== expected) {
throw new Error("At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
}
}
export function assertNull(actual: any, message?: string) {
if (actual !== null && actual !== undefined) {
throw new Error(message + " Actual: " + actual + " is not null/undefined");
}
}
export function assertNotNull(actual: any, message?: string) {
if (actual === null || actual === undefined) {
throw new Error(message + " Actual: " + actual + " is null/undefined");
}
}
export function areClose(actual: number, expected: number, delta: number): boolean {
if (isNaN(actual) || Math.abs(actual - expected) > delta) {
return false;
}
return true;
}
export function assertAreClose(actual: number, expected: number, delta: number, message?: string) {
if (!areClose(actual, expected, delta)) {
throw new Error(message + " Numbers are not close enough. Actual: " + actual + " Expected: " + expected + " Delta: " + delta);
}
}
export function assertMatches(actual: string, expected: RegExp, message?: string) {
if (expected.test(actual) !== true) {
throw new Error(`"${actual}" doesn't match "${expected}". ${message}`);
}
}
export function arrayAssert(actual: Array<any>, expected: Array<any>, message?: string) {
if (actual.length !== expected.length) {
throw new Error(message + " Actual array length: " + actual.length + " Expected array length: " + expected.length);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) {
throw new Error(message + " Actual element at " + i + " is: " + actual[i] + " Expected element is: " + expected[i]);
}
}
}
export function assertThrows(testFunc: () => void, assertMessage?: string, expectedMessage?: string) {
const re = expectedMessage ? new RegExp(`^${expectedMessage}$`) : null;
return assertThrowsRegExp(testFunc, assertMessage, re);
}
export function assertThrowsRegExp(testFunc: () => void, assertMessage?: string, expectedMessage?: RegExp) {
let actualError: Error;
try {
testFunc();
} catch (e) {
actualError = e;
}
if (!actualError) {
throw new Error("Missing expected exception. " + assertMessage);
}
if (expectedMessage && !expectedMessage.test(actualError.message)) {
throw new Error("Got unwanted exception. Actual error: " + actualError.message + " Expected to match: " + expectedMessage);
}
}
export function wait(seconds: number): void {
waitUntilReady(() => false, seconds, false);
}
export function waitUntilReady(isReady: () => boolean, timeoutSec: number = 3, shouldThrow: boolean = true) {
if (!isReady) {
return;
}
if (application.ios) {
const timeoutMs = timeoutSec * 1000;
let totalWaitTime = 0;
while (true) {
const begin = time();
const currentRunLoop = NSRunLoop.currentRunLoop;
currentRunLoop.limitDateForMode(currentRunLoop.currentMode);
if (isReady()) {
break;
}
totalWaitTime += (time() - begin);
if (totalWaitTime >= timeoutMs) {
if (shouldThrow) {
throw new Error("waitUntilReady Timeout.");
} else {
break;
}
}
}
} else if (application.android) {
doModalAndroid(isReady, timeoutSec, shouldThrow);
}
}
// Setup for the Android modal loop implementation
// TODO: If these platform-specific implementations continue to grow, think of per-platform separation (TKUnit.android)
let nextMethod;
let targetField;
let prepared;
function prepareModal() {
if (prepared) {
return;
}
const clsMsgQueue = java.lang.Class.forName("android.os.MessageQueue");
const clsMsg = java.lang.Class.forName("android.os.Message");
const methods = clsMsgQueue.getDeclaredMethods();
for (let i = 0; i < methods.length; i++) {
if (methods[i].getName() === "next") {
nextMethod = methods[i];
nextMethod.setAccessible(true);
break;
}
}
const fields = clsMsg.getDeclaredFields();
for (let i = 0; i < fields.length; i++) {
if (fields[i].getName() === "target") {
targetField = fields[i];
targetField.setAccessible(true);
break;
}
}
prepared = true;
}
function doModalAndroid(quitLoop: () => boolean, timeoutSec: number, shouldThrow: boolean = true) {
if (!quitLoop) {
return;
}
prepareModal();
const queue = android.os.Looper.myQueue();
let quit = false;
let timeout = false;
timer.setTimeout(() => {
quit = true;
timeout = true;
}, timeoutSec * 1000);
let msg;
while (!quit) {
msg = nextMethod.invoke(queue, null);
if (msg) {
const target = targetField.get(msg);
if (!target) {
quit = true;
} else {
target.dispatchMessage(msg);
}
if (sdkVersion < 21) {//https://code.google.com/p/android-test-kit/issues/detail?id=84
msg.recycle();
}
}
if (shouldThrow && timeout) {
throw new Error("waitUntilReady Timeout.");
}
if (!quit && quitLoop()) {
quit = true;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -0,0 +1,33 @@
/**
This file defines a decorator you can use to enable two-way
binding on view-model properties.
For example:
import { ObservableProperty } from "../observable-property-decorator";
@ObservableProperty() myProperty: boolean = true;
Read more at https://www.nativescript.org/blog/nativescript-observable-magic-string-property-name-be-gone
**/
import { Observable } from 'tns-core-modules/data/observable';
export function ObservableProperty() {
return (target: Observable, propertyKey: string) => {
Object.defineProperty(target, propertyKey, {
get() {
return this["_" + propertyKey];
},
set(value) {
if (this["_" + propertyKey] === value) {
return;
}
this["_" + propertyKey] = value;
this.notifyPropertyChange(propertyKey, value);
},
enumerable: true,
configurable: true
});
};
}

View File

@ -1,9 +1,46 @@
import { EventData } from "tns-core-modules/data/observable";
import { EventData, fromObject } from "tns-core-modules/data/observable";
import { ListView, ItemEventData } from "tns-core-modules/ui/list-view";
import { Page } from "tns-core-modules/ui/page";
import { HelloWorldModel } from "./main-view-model";
export function navigatingTo(args: EventData) {
export function onNavigatingTo(args: EventData) {
const page = <Page>args.object;
page.bindingContext = new HelloWorldModel();
const vm = fromObject({
// Setting the listview binding source
myTitles: [
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" },
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" },
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" },
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" },
]
});
page.bindingContext = vm;
}
export function onListViewLoaded(args: EventData) {
const listView = <ListView>args.object;
}
export function onItemTap(args: ItemEventData) {
const index = args.index;
console.log(`Second ListView item tap ${index}`);
}

View File

@ -1,5 +1,22 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" id="page" navigatingTo="navigatingTo">
<Page xmlns="http://schemas.nativescript.org/tns.xsd" id="page" navigatingTo="onNavigatingTo">
<StackLayout id="stack">
<Label id="label" text="{{ message }}"></Label>
<!-- <Button id="label" text="{{ message }}" height="100" width="100" color="blue" backgroundColor="red"></Button> -->
<ListView items="{{ myTitles }}"
itemTap="onItemTap"
loaded="{{ onListViewLoaded }}"
separatorColor="orangered" rowHeight="50"
class="list-group" id="listView" row="2">
<ListView.itemTemplate>
<!-- The item template can only have a single root view container (e.g. GriLayout, StackLayout, etc.) -->
<StackLayout class="list-group-item">
<Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" />
<Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" />
<Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
</StackLayout>
</Page>

View File

@ -0,0 +1,138 @@
import { renderFixture } from "@nativescript/core/testing";
import { assert } from "chai";
import { Page } from "@nativescript/core/ui/page/page";
import { ListView } from "@nativescript/core/ui/list-view/list-view";
import { waitUntilLayoutReady } from "../home/home-page";
import { waitUntilReady } from "../home/tk-unit";
// const snippet = `
// <StackLayout id="stack">
// <Label id="label" text="Label"></Label>
// </StackLayout>`
function asdf() {
// `delay` returns a promise
return new Promise(function (resolve, reject) {
// Only `delay` is able to resolve or reject the promise
// setTimeout(function () {
// resolve(42); // After 3 seconds, resolve the promise with value 42
console.log("---> pro");
const page = <Page>renderFixture(true, "home/home-page");
const listView = <ListView>page.getViewById("list-view");
waitUntilReady(() => listView.isLayoutValid);
console.log("---> middle");
waitUntilReady(() => page.isLayoutValid);
console.log("---> end");
resolve(page);
// }, 0);
});
}
describe("---> describe", function () {
// // it("---> snippet", function () {
// // const rootView = renderFixture(false, snippet);
// // const label = <Label>rootView.getViewById("label");
// // const actualText = label.text;
// // const expectedText = "Labe";
// // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // });
// // it("---> page", function () {
// // const rootView = renderFixture(true, "test-page");
// // const label = <Label>rootView.getViewById("label");
// // const actualText = label.text;
// // const expectedText = "Test Page";
// // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // });
it("---> navigate to module", function (done) {
asdf().then(p => {
console.log("---> page", p);
done();
});
// const page = <Page>renderFixture(true, "home/home-page");
// listView.on(ListView.itemLoadingEvent, function (args) {
// console.log("aaa");
// counter++;
// console.log("bbb");
// console.log(counter);
// console.log(listView.items.length);
// if (counter === listView.items.length) {
// console.log("sdf");
// // waitUntilLayoutReady(listView.items[0]);
// // waitUntilReady(() => );
// console.log("yey");
// done();
// }
// });
// console.log("--->", listView);
// const listView = <ListView>page.getViewById("list-view");
// waitUntilReady(() => listView.isLayoutValid);
// waitUntilReady(() => page.isLayoutValid);
// const p = new Promise(function (resolve, reject) {
// });
// console.l}og("--->", listView.items.length);
// const items = listView.items;
// console.log("--->", items.size);
// const actualText = label.text;
// const size = label.size;
// const height = label.height;
// const width = label.width;
// const position = label.position;
// const style = label.style.backgroundColor;
// const color = label.style.color;
// const nativeView = label.nativeView;
// const x = label.nativeView.originX;
// const asdf = label.getLocationInWindow();
// const expectedText = "42 taps";
// // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// console.log("---> sssize", size);
// console.log("---> height", height);
// console.log("---> width", width);
// console.log("---> position", position);
// console.log("---> style", style);
// console.log("---> color", color);
// console.log("---> nativeView", nativeView);
// console.log("---> x", x);
// console.log("---> asdf", asdf);
});
// // it("this is a test", () => {
// // // .when()
// // // .waitUntilReady()
// // // .waitUntilLayoutIsValid()
// // setup("test-module").then(fixture => {
// // const rootView = fixture.root;
// // const label = rootView.getViewById("label");
// // const labelText = label.text;
// // const labelHeight = label.labelHeight;
// // // label size
// // // label position
// // // label.style. ...
// // // label.nativeView
// // // label vizualise
// // // assert
// // // list view async loading
// // // list view async loading
// // // tab view
// // // tab view
// // });
// // });
});

View File

@ -1,40 +1,156 @@
import { Builder, Label, Frame } from "@nativescript/core/ui";
// import { renderFixture } from "@nativescript/core/testing";
// import { assert } from "chai";
// import { Page } from "@nativescript/core/ui/page/page";
// import { ListView } from "@nativescript/core/ui/list-view/list-view";
// import { waitUntilLayoutReady } from "../home/home-page";
// import { waitUntilReady } from "../home/tk-unit";
import { navigateToModule, asdf, render } from "@nativescript/core/testing/ui-helper";
import { assert } from "@nativescript/core/testing/tk-unit";
// // const snippet = `
// // <StackLayout id="stack">
// // <Label id="label" text="Label"></Label>
// // </StackLayout>`
const snippet = `
<StackLayout id="stack">
<Label id="label" text="Label"></Label>
</StackLayout>`
// describe("---> describe", function () {
// // // it("---> snippet", function () {
// // // const rootView = renderFixture(false, snippet);
// // // const label = <Label>rootView.getViewById("label");
describe("---> describe", function () {
// it("---> snippet", function (done) {
// const rootView = Builder.parse(snippet);
// const label = <Label>rootView.getViewById("label");
// // // const actualText = label.text;
// // // const expectedText = "Labe";
// const actualText = label.text;
// const expectedText = "Label";
// // // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // // });
// assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// done();
// // // it("---> page", function () {
// // // const rootView = renderFixture(true, "test-page");
// // // const label = <Label>rootView.getViewById("label");
// // // const actualText = label.text;
// // // const expectedText = "Test Page";
// // // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // // });
// it("---> navigate to module", async () => {
// // const page = <Page>renderFixture(true, "home/home-page");
// // listView.on(ListView.itemLoadingEvent, function (args) {
// // console.log("aaa");
// // counter++;
// // console.log("bbb");
// // console.log(counter);
// // console.log(listView.items.length);
// // if (counter === listView.items.length) {
// // console.log("sdf");
// // // waitUntilLayoutReady(listView.items[0]);
// // // waitUntilReady(() => );
// // console.log("yey");
// // done();
// // }
// // });
// // console.log("--->", listView);
// // const listView = <ListView>page.getViewById("list-view");
// // waitUntilReady(() => listView.isLayoutValid);
// // waitUntilReady(() => page.isLayoutValid);
// // const p = new Promise(function (resolve, reject) {
// // });
// async function asdf() {
// // `delay` returns a promise
// return new Promise(function (resolve, reject) {
// // Only `delay` is able to resolve or reject the promise
// // setTimeout(function () {
// // resolve(42); // After 3 seconds, resolve the promise with value 42
// console.log("---> pro");
// const page = <Page>renderFixture(true, "home/home-page");
// const listView = <ListView>page.getViewById("list-view");
// waitUntilReady(() => listView.isLayoutValid);
// console.log("---> middle");
// waitUntilReady(() => page.isLayoutValid);
// console.log("---> end");
// resolve(page);
// // }, 0);
// });
// }
// function good(page) {
// console.log("---> page", page);
// // cb();
// // done();
// }
// function bad(e) {
// console.error(e);
// // cb();done
// // done();
// }
// const awef = await asdf();
// good(awef);
// // .then(
// // good,
// // bad
// // // ).then(
// // // done()
// // );
// // done();
// // console.log("--->", listView.items.length);
// // const items = listView.items;
// // console.log("--->", items.size);
// // const actualText = label.text;
// // const size = label.size;
// // const height = label.height;
// // const width = label.width;
// // const position = label.position;
// // const style = label.style.backgroundColor;
// // const color = label.style.color;
// // const nativeView = label.nativeView;
// // const x = label.nativeView.originX;
// // const asdf = label.getLocationInWindow();
// // const expectedText = "42 taps";
// // // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // console.log("---> sssize", size);
// // console.log("---> height", height);
// // console.log("---> width", width);
// // console.log("---> position", position);
// // console.log("---> style", style);
// // console.log("---> color", color);
// // console.log("---> nativeView", nativeView);
// // console.log("---> x", x);
// // console.log("---> asdf", asdf);
// });
// it("---> page", function () {
// const rootView = Builder.load("test-page");
// const label = <Label>rootView.getViewById("label");
// // // it("this is a test", () => {
// // // // .when()
// // // // .waitUntilReady()
// // // // .waitUntilLayoutIsValid()
// // // setup("test-module").then(fixture => {
// // // const rootView = fixture.root;
// // // const label = rootView.getViewById("label");
// // // const labelText = label.text;
// // // const labelHeight = label.labelHeight;
// // // // label size
// // // // label position
// // // // label.style. ...
// // // // label.nativeView
// // // // label vizualise
// const actualText = label.text;
// const expectedText = "Test Page Stack Label";
// // // // assert
// // // // list view async loading
// // // // list view async loading
// // // // tab view
// // // // tab view
// // // });
// // // });
// assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// });
it("---> navigate to module", () => {
const page = navigateToModule("test-module");
const label = page.getViewById("label");
const actualText = label.text;
const expectedText = "42 taps left";
assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
});
});

View File

@ -0,0 +1,186 @@
// // import { RadListView, ListViewEventData, ListViewLinearLayout, ListViewScrollDirection } from "nativescript-ui-listview";
// import { topmost } from "tns-core-modules/ui/frame";
// import { Page } from "tns-core-modules/ui/page";
// import { Label } from "tns-core-modules/ui/label";
// import { isAndroid, isIOS } from "tns-core-modules/platform";
// import { ObservableArray } from "tns-core-modules/data/observable-array/observable-array";
// import * as TKUnit from "../home/tk-unit";
// import { ListView } from "tns-core-modules/ui/list-view/list-view";
// interface Item {
// text: string;
// age: number;
// loadedCount: number;
// unloadedCount: number;
// onViewLoaded: (args) => void;
// onViewUnloaded: (args) => void;
// }
// function generateCollection(count: number): ObservableArray<string> {
// let obsArray = new ObservableArray<any>();
// for (let i = 0; i < count; i++) {
// obsArray.push(" Item " + i);
// }
// return obsArray;
// }
// function generateItemsForMultipleTemplatesTests(count: number): ObservableArray<Item> {
// let items = new ObservableArray<Item>();
// for (let i = 0; i < count; i++) {
// items.push({
// text: "Item " + i,
// age: i,
// loadedCount: 0,
// unloadedCount: 0,
// onViewLoaded: function onViewLoaded(args) {
// this.loadedCount++;
// },
// onViewUnloaded: function onViewUnloaded(args) {
// this.unloadedCount++;
// }
// });
// }
// return items;
// }
// function navigate(listView) {
// topmost().navigate({
// create: () => {
// const page = new Page();
// page.content = listView;
// return page;
// },
// clearHistory: true
// });
// }
// describe("example tests", () => {
// let listView = null;
// beforeEach(() => {
// listView = new ListView();
// });
// afterEach(() => {
// // if (listView) {
// // listView.removeAllListeners();
// // }
// });
// it("test_default_TNS_values", () => {
// assert.isUndefined(listView.items);
// });
// it("test_set_items_to_array_loads_all_items", (done) => {
// const colors = ["red", "green", "blue"];
// const indexes = {};
// let counter = 0;
// listView.items = colors;
// listView.on(ListView.itemLoadingEvent, function (args) {
// console.log("aaa");
// counter++;
// console.log("bbb");
// console.log(counter);
// console.log(listView.items.length);
// if (counter === listView.items.length) {
// console.log("sdf");
// }
// });
// console.log("---> counter", counter);
// console.log("---");
// navigate(listView);
// console.log("+++");
// console.log("---> counter", listView.items.length);
// done();
// });
// // it("test_set_items_to_obs_array_loads_items_horizontally", (done) => {
// // const linearLayout = new ListViewLinearLayout();
// // linearLayout.scrollDirection = ListViewScrollDirection.Vertical;
// // listView.listViewLayout = linearLayout;
// // // let isScrolled = false;
// // const loadedIndexes = [];
// // const items = generateCollection(50);
// // listView.items = items;
// // listView.on(ListView.itemLoadingEvent, function (args: ListViewEventData) {
// // loadedIndexes[args.index] = true;
// // if (args.index === 48) {
// // done();
// // }
// // });
// // navigate(listView);
// // TKUnit.waitUntilReady(() => loadedIndexes[5] === true);
// // listView.scrollToIndex(49);
// // TKUnit.waitUntilReady(() => loadedIndexes[48] === true);
// // });
// // it("test_set_items_to_obs_array_scroll_horizontally", () => {
// // const linearLayout = new ListViewLinearLayout();
// // listView.listViewLayout = linearLayout;
// // linearLayout.scrollDirection = ListViewScrollDirection.Vertical;
// // const loadedIndexes = [];
// // listView.on(RadListView.itemLoadingEvent, function (args: ListViewEventData) {
// // loadedIndexes[args.index] = true;
// // });
// // const items = generateCollection(50);
// // listView.items = items;
// // navigate(listView);
// // TKUnit.waitUntilReady(() => loadedIndexes[5] === true);
// // listView.scrollToIndex(35);
// // TKUnit.waitUntilReady(() => loadedIndexes[35] === true);
// // // assert the next item is not loaded
// // assert.isFalse(!!loadedIndexes[36]);
// // });
// // it("test_getScrollOffset_respects_safe_areas", () => {
// // const loadedIndexes = [];
// // listView.on(RadListView.itemLoadingEvent, function (args: ListViewEventData) {
// // loadedIndexes[args.index] = true;
// // });
// // const items = generateCollection(10);
// // listView.items = items;
// // navigate(listView);
// // TKUnit.waitUntilReady(() => loadedIndexes[5] === true);
// // assert.isTrue(listView.getScrollOffset() === 0);
// // });
// // it("test_set_items_to_huge_obs_array", () => {
// // listView.itemTemplate = `
// // <StackLayout>
// // <Label text='{{ text }}' />
// // <Label text='{{ age }}' />
// // </StackLayout>
// // `;
// // const start = TKUnit.time();
// // const items = generateItemsForMultipleTemplatesTests(10000);
// // listView.items = items;
// // const loadedIndexes = [];
// // listView.on(RadListView.itemLoadingEvent, function (args: ListViewEventData) {
// // loadedIndexes[args.index] = true;
// // });
// // TKUnit.waitUntilReady(() => loadedIndexes[10] === true);
// // assert.isTrue(loadedIndexes[10]);
// // const end = TKUnit.time() - start;
// // console.log("================= END ============= ", end);
// // });
// });

View File

@ -38,24 +38,24 @@
// // });
// // it("---> module", function (done) {
// // // render done
// // asdf("test-module").then((fixture) => {
// // const label = fixture.getViewById("label");
// // const actualText = label.text;
// // const expectedText = "42 taps ";
// // assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// // done();
// // });//.finally(done);
// // render done
// asdf("test-module").then((done) => {
// const label = fixture.getViewById("label");
// const actualText = label.text;
// const expectedText = "42 taps ";
// assert(actualText === expectedText, `Actual: ${actualText}, Expected: ${expectedText}`);
// done();
// });//.finally(done);
// // });
// // it('resolves', (done) => {
// // const resolvingPromise = new Promise((resolve) => {
// // resolve('promiseresolved');
// // });
// // resolvingPromise.then((result) => {
// // expect(result).to.equal('promise resolved');
// // }).finally(done);
// // });
// it('resolves', (done) => {
// const resolvingPromise = new Promise((resolve) => {
// resolve('promiseresolved');
// });
// resolvingPromise.then((result) => {
// expect(result).to.equal('promise resolved');
// }).finally(done);
// });
// // });
// const resolvingPromise = new Promise((resolve) =>