diff --git a/tests/app/app/app-new.css b/tests/app/app/app-new.css
new file mode 100644
index 000000000..2b3afb687
--- /dev/null
+++ b/tests/app/app/app-new.css
@@ -0,0 +1,3 @@
+Button, Label {
+ color: green;
+}
diff --git a/tests/app/app/app-new.scss b/tests/app/app/app-new.scss
new file mode 100644
index 000000000..2b3afb687
--- /dev/null
+++ b/tests/app/app/app-new.scss
@@ -0,0 +1,3 @@
+Button, Label {
+ color: green;
+}
diff --git a/tests/app/app/application.css b/tests/app/app/application.css
new file mode 100644
index 000000000..119b358a7
--- /dev/null
+++ b/tests/app/app/application.css
@@ -0,0 +1,3 @@
+Button, Label {
+ color: black;
+}
diff --git a/tests/app/app/mainPage.ts b/tests/app/app/mainPage.ts
index 70e2ce2e7..4c16fca02 100644
--- a/tests/app/app/mainPage.ts
+++ b/tests/app/app/mainPage.ts
@@ -2,6 +2,8 @@
import * as trace from "tns-core-modules/trace";
import * as tests from "../testRunner";
+let executeTests = true;
+
trace.enable();
trace.addCategories(trace.categories.Test + "," + trace.categories.Error);
@@ -21,6 +23,8 @@ function runTests() {
export function onNavigatedTo(args) {
args.object.off(Page.loadedEvent, onNavigatedTo);
-
- runTests();
+ if (executeTests) {
+ executeTests = false;
+ runTests();
+ }
}
diff --git a/tests/app/app/mainPage.xml b/tests/app/app/mainPage.xml
index bee9a71d8..7cead486d 100644
--- a/tests/app/app/mainPage.xml
+++ b/tests/app/app/mainPage.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/tests/app/livesync/livesync-tests.ts b/tests/app/livesync/livesync-tests.ts
new file mode 100644
index 000000000..8ae772203
--- /dev/null
+++ b/tests/app/livesync/livesync-tests.ts
@@ -0,0 +1,113 @@
+import * as app from "tns-core-modules/application/application";
+import * as frame from "tns-core-modules/ui/frame";
+import * as helper from "../ui/helper";
+import * as TKUnit from "../TKUnit";
+import { Color } from "tns-core-modules/color";
+import { parse } from "tns-core-modules/ui/builder";
+import { Page } from "tns-core-modules/ui/page";
+
+const appCssFileName = "./app/application.css";
+const appNewCssFileName = "./app/app-new.css";
+const appNewScssFileName = "./app/app-new.scss";
+const appJsFileName = "./app/app.js";
+const appTsFileName = "./app/app.ts";
+const mainPageCssFileName = "./app/main-page.css";
+const mainPageHtmlFileName = "./app/main-page.html";
+const mainPageXmlFileName = "./app/main-page.xml";
+
+const green = new Color("green");
+
+const mainPageTemplate = `
+
+
+
+
+ `;
+
+const pageTemplate = `
+
+
+
+
+ `;
+
+export function test_onLiveSync_HmrContext_AppStyle_AppNewCss() {
+ _test_onLiveSync_HmrContext_AppStyle(appNewCssFileName);
+}
+
+export function test_onLiveSync_HmrContext_AppStyle_AppNewScss() {
+ _test_onLiveSync_HmrContext_AppStyle(appNewScssFileName);
+}
+
+export function test_onLiveSync_HmrContext_ContextUndefined() {
+ _test_onLiveSync_HmrContext({ type: undefined, module: undefined });
+}
+
+export function test_onLiveSync_HmrContext_ModuleUndefined() {
+ _test_onLiveSync_HmrContext({ type: "script", module: undefined });
+}
+
+export function test_onLiveSync_HmrContext_Script_AppJs() {
+ _test_onLiveSync_HmrContext({ type: "script", module: appJsFileName });
+}
+
+export function test_onLiveSync_HmrContext_Script_AppTs() {
+ _test_onLiveSync_HmrContext({ type: "script", module: appTsFileName });
+}
+
+export function test_onLiveSync_HmrContext_Style_MainPageCss() {
+ _test_onLiveSync_HmrContext({ type: "style", module: mainPageCssFileName });
+}
+
+export function test_onLiveSync_HmrContext_Markup_MainPageHtml() {
+ _test_onLiveSync_HmrContext({ type: "markup", module: mainPageHtmlFileName });
+}
+
+export function test_onLiveSync_HmrContext_Markup_MainPageXml() {
+ _test_onLiveSync_HmrContext({ type: "markup", module: mainPageXmlFileName });
+}
+
+export function setUpModule() {
+ const mainPage = parse(mainPageTemplate);
+ helper.navigate(() => mainPage);
+}
+
+export function tearDown() {
+ app.setCssFileName(appCssFileName);
+}
+
+function _test_onLiveSync_HmrContext_AppStyle(styleFileName: string) {
+ const pageBeforeNavigation = helper.getCurrentPage();
+
+ const page = parse(pageTemplate);
+ helper.navigateWithHistory(() => page);
+ app.setCssFileName(styleFileName);
+
+ const pageBeforeLiveSync = helper.getCurrentPage();
+ global.__onLiveSync({ type: "style", module: styleFileName });
+
+ const pageAfterLiveSync = helper.getCurrentPage();
+ TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
+
+ TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "App styles NOT applied - livesync navigation executed!");
+ TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!");
+ TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version 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 - livesync navigation executed!");
+ TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
+}
+
+function _test_onLiveSync_HmrContext(context: { type, module }) {
+ const page = parse(pageTemplate);
+ helper.navigateWithHistory(() => page);
+ global.__onLiveSync({ type: context.type, module: context.module });
+
+ TKUnit.waitUntilReady(() => !!frame.topmost());
+ const topmostFrame = frame.topmost();
+ TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && !topmostFrame.canGoBack());
+ TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded);
+}
\ No newline at end of file
diff --git a/tests/app/testRunner.ts b/tests/app/testRunner.ts
index a119642a0..b96bde95d 100644
--- a/tests/app/testRunner.ts
+++ b/tests/app/testRunner.ts
@@ -153,9 +153,6 @@ allTests["STYLE-PROPERTIES"] = stylePropertiesTests;
import * as frameTests from "./ui/frame/frame-tests";
allTests["FRAME"] = frameTests;
-import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests";
-allTests["TAB-VIEW-ROOT"] = tabViewRootTests;
-
import * as viewTests from "./ui/view/view-tests";
allTests["VIEW"] = viewTests;
@@ -255,6 +252,12 @@ allTests["SEARCH-BAR"] = searchBarTests;
import * as navigationTests from "./navigation/navigation-tests";
allTests["NAVIGATION"] = navigationTests;
+import * as livesyncTests from "./livesync/livesync-tests";
+allTests["LIVESYNC"] = livesyncTests;
+
+import * as tabViewRootTests from "./ui/tab-view/tab-view-root-tests";
+allTests["TAB-VIEW-ROOT"] = tabViewRootTests;
+
import * as resetRootViewTests from "./ui/root-view/reset-root-view-tests";
allTests["RESET-ROOT-VIEW"] = resetRootViewTests;
diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts
index 32ab9dc04..3f0d0888c 100644
--- a/tns-core-modules/application/application-common.ts
+++ b/tns-core-modules/application/application-common.ts
@@ -32,7 +32,14 @@ export function hasLaunched(): boolean {
export { Observable };
-import { UnhandledErrorEventData, iOSApplication, AndroidApplication, CssChangedEventData, LoadAppCSSEventData } from ".";
+import {
+ AndroidApplication,
+ CssChangedEventData,
+ getRootView,
+ iOSApplication,
+ LoadAppCSSEventData,
+ UnhandledErrorEventData
+} from "./application";
export { UnhandledErrorEventData, CssChangedEventData, LoadAppCSSEventData };
@@ -70,10 +77,21 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v
app = instance;
}
-export function livesync() {
+export function livesync(context?: HmrContext) {
events.notify({ eventName: "livesync", object: app });
const liveSyncCore = global.__onLiveSyncCore;
- if (liveSyncCore) {
+ let reapplyAppCss = false
+
+ if (context) {
+ const fullFileName = getCssFileName();
+ const fileName = fullFileName.substring(0, fullFileName.lastIndexOf(".") + 1);
+ const extensions = ["css", "scss"];
+ reapplyAppCss = extensions.some(ext => context.module === fileName.concat(ext));
+ }
+
+ if (reapplyAppCss) {
+ getRootView()._onCssStateChange();
+ } else if (liveSyncCore) {
liveSyncCore();
}
}
@@ -92,7 +110,7 @@ export function loadAppCss(): void {
events.notify({ eventName: "loadAppCss", object: app, cssFile: getCssFileName() });
} catch (e) {
throw new Error(`The file ${getCssFileName()} couldn't be loaded! ` +
- `You may need to register it inside ./app/vendor.ts.`);
+ `You may need to register it inside ./app/vendor.ts.`);
}
}
diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts
index 0b33bba80..6d5166675 100644
--- a/tns-core-modules/application/application.android.ts
+++ b/tns-core-modules/application/application.android.ts
@@ -212,12 +212,12 @@ export function getNativeApplication(): android.app.Application {
return nativeApp;
}
-global.__onLiveSync = function () {
+global.__onLiveSync = function __onLiveSync(context?: HmrContext) {
if (androidApp && androidApp.paused) {
return;
}
- livesync();
+ livesync(context);
};
function initLifecycleCallbacks() {
diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts
index 867f2ef6f..af6028cd4 100644
--- a/tns-core-modules/application/application.ios.ts
+++ b/tns-core-modules/application/application.ios.ts
@@ -161,7 +161,7 @@ class IOSApplication implements IOSApplicationDefinition {
this.setWindowContent(args.root);
} else {
this._window = UIApplication.sharedApplication.delegate.window;
- }
+ }
}
@profile
@@ -373,10 +373,10 @@ function setViewControllerView(view: View): void {
}
}
-global.__onLiveSync = function () {
+global.__onLiveSync = function __onLiveSync(context?: HmrContext) {
if (!started) {
return;
}
- livesync();
-}
+ livesync(context);
+}
\ No newline at end of file
diff --git a/tns-core-modules/module.d.ts b/tns-core-modules/module.d.ts
index 5fd5d161d..0a0f6a033 100644
--- a/tns-core-modules/module.d.ts
+++ b/tns-core-modules/module.d.ts
@@ -51,7 +51,7 @@ declare namespace NodeJS {
__native?: any;
__inspector?: any;
__extends: any;
- __onLiveSync: () => void;
+ __onLiveSync: (context?: { type: string, module: string }) => void;
__onLiveSyncCore: () => void;
__onUncaughtError: (error: NativeScriptError) => void;
TNS_WEBPACK?: boolean;
@@ -64,6 +64,27 @@ declare function clearTimeout(timeoutId: number): void;
declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): number;
declare function clearInterval(intervalId: number): void;
+declare enum HmrType {
+ markup = "markup",
+ script = "script",
+ style = "style"
+}
+
+/**
+ * Define a context for Hot Module Replacement.
+ */
+interface HmrContext {
+ /**
+ * The type of module for replacement.
+ */
+ type: HmrType;
+
+ /**
+ * The module for replacement.
+ */
+ module: string;
+}
+
/**
* An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code.
*/
diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts
index 1f56a8eb2..6756bf915 100644
--- a/tns-core-modules/ui/frame/frame.android.ts
+++ b/tns-core-modules/ui/frame/frame.android.ts
@@ -1198,4 +1198,4 @@ export function setActivityCallbacks(activity: android.support.v7.app.AppCompatA
export function setFragmentCallbacks(fragment: android.support.v4.app.Fragment): void {
fragment[CALLBACKS] = new FragmentCallbacksImplementation();
-}
+}
\ No newline at end of file
diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts
index 0d85ab1ab..28814e186 100644
--- a/tns-core-modules/ui/page/page-common.ts
+++ b/tns-core-modules/ui/page/page-common.ts
@@ -17,17 +17,17 @@ export class PageBase extends ContentView implements PageDefinition {
public static navigatedToEvent = "navigatedTo";
public static navigatingFromEvent = "navigatingFrom";
public static navigatedFromEvent = "navigatedFrom";
-
+
private _navigationContext: any;
private _actionBar: ActionBar;
public _frame: Frame;
-
+
public actionBarHidden: boolean;
public enableSwipeBackNavigation: boolean;
public backgroundSpanUnderStatusBar: boolean;
public hasActionBar: boolean;
-
+
get navigationContext(): any {
return this._navigationContext;
}
@@ -89,7 +89,7 @@ export class PageBase extends ContentView implements PageDefinition {
const frame = this.parent;
return frame instanceof Frame ? frame : undefined;
}
-
+
private createNavigatedData(eventName: string, isBackNavigation: boolean): NavigatedData {
return {
eventName: eventName,
@@ -103,6 +103,13 @@ export class PageBase extends ContentView implements PageDefinition {
public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) {
this._navigationContext = context;
+ if (isBackNavigation && this._styleScope) {
+ this._styleScope.ensureSelectors();
+ if (!this._cssState.isSelectorsLatestVersionApplied()) {
+ this._onCssStateChange();
+ }
+ }
+
//https://github.com/NativeScript/NativeScript/issues/731
if (!isBackNavigation && bindingContext !== undefined && bindingContext !== null) {
this.bindingContext = bindingContext;
diff --git a/tns-core-modules/ui/styling/style-scope.d.ts b/tns-core-modules/ui/styling/style-scope.d.ts
index b404ce9a7..b5bf01684 100644
--- a/tns-core-modules/ui/styling/style-scope.d.ts
+++ b/tns-core-modules/ui/styling/style-scope.d.ts
@@ -19,6 +19,11 @@ export class CssState {
* Gets the static selectors that match the view and the dynamic selectors that may potentially match the view.
*/
public changeMap: ChangeMap;
+
+ /**
+ * Checks whether style scope and CSS state selectors are in sync.
+ */
+ public isSelectorsLatestVersionApplied(): boolean
}
export class StyleScope {
@@ -29,6 +34,9 @@ export class StyleScope {
public static createSelectorsFromImports(tree: SyntaxTree, keyframes: Object): RuleSet[];
public ensureSelectors(): number;
+ public isApplicationCssSelectorsLatestVersionApplied(): boolean;
+ public isLocalCssSelectorsLatestVersionApplied(): boolean;
+
public applySelectors(view: ViewBase): void
public query(options: Node): SelectorCore[];
diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts
index 8f3cf607c..a915b559d 100644
--- a/tns-core-modules/ui/styling/style-scope.ts
+++ b/tns-core-modules/ui/styling/style-scope.ts
@@ -271,7 +271,7 @@ export function removeTaggedAdditionalCSS(tag: String | Number): Boolean {
changed = true;
}
}
- if (changed) { mergeCssSelectors(); }
+ if (changed) { mergeCssSelectors(); }
return changed;
}
@@ -343,6 +343,7 @@ export class CssState {
_appliedChangeMap: Readonly>;
_appliedPropertyValues: Readonly<{}>;
_appliedAnimations: ReadonlyArray;
+ _appliedSelectorsVersion: number;
_match: SelectorsMatch;
_matchInvalid: boolean;
@@ -367,6 +368,10 @@ export class CssState {
}
}
+ public isSelectorsLatestVersionApplied(): boolean {
+ return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion;
+ }
+
public onLoaded(): void {
if (this._matchInvalid) {
this.updateMatch();
@@ -381,7 +386,12 @@ export class CssState {
@profile
private updateMatch() {
- this._match = this.view._styleScope ? this.view._styleScope.matchSelectors(this.view) : CssState.emptyMatch;
+ if (this.view._styleScope) {
+ this._appliedSelectorsVersion = this.view._styleScope._getSelectorsVersion();
+ this._match = this.view._styleScope.matchSelectors(this.view);
+ } else {
+ this._match = CssState.emptyMatch;
+ }
this._matchInvalid = false;
}
@@ -597,8 +607,8 @@ export class StyleScope {
}
public ensureSelectors(): number {
- if (this._applicationCssSelectorsAppliedVersion !== applicationCssSelectorVersion ||
- this._localCssSelectorVersion !== this._localCssSelectorsAppliedVersion ||
+ if (!this.isApplicationCssSelectorsLatestVersionApplied() ||
+ !this.isLocalCssSelectorsLatestVersionApplied() ||
!this._mergedCssSelectors) {
this._createSelectors();
@@ -607,6 +617,14 @@ export class StyleScope {
return this._getSelectorsVersion();
}
+ public isApplicationCssSelectorsLatestVersionApplied(): boolean {
+ return this._applicationCssSelectorsAppliedVersion === applicationCssSelectorVersion;
+ }
+
+ public isLocalCssSelectorsLatestVersionApplied(): boolean {
+ return this._localCssSelectorsAppliedVersion === this._localCssSelectorVersion;
+ }
+
@profile
private _createSelectors() {
let toMerge: RuleSet[][] = [];