From 42a1491e6e089200414f7dc13718e7a9380945c7 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 4 Oct 2018 19:20:13 +0300 Subject: [PATCH 1/5] feat(HMR): apply changes in application styles at runtime Expose `HmrContext` interface. Apply changes in `app.css` instantly. Avoid navigation on livesync when changes in `app.css` have been made. Apply changes in `app.css` on back navigation. --- .../application/application-common.ts | 6 ++-- .../application/application.android.ts | 4 +-- .../application/application.ios.ts | 28 +++++++++++----- tns-core-modules/module.d.ts | 25 +++++++++++++-- tns-core-modules/ui/frame/frame.android.ts | 32 +++++++++++++------ tns-core-modules/ui/page/page-common.ts | 12 ++++--- tns-core-modules/ui/styling/style-scope.d.ts | 8 +++++ tns-core-modules/ui/styling/style-scope.ts | 27 +++++++++++++--- tsconfig.shared.json | 2 +- 9 files changed, 110 insertions(+), 34 deletions(-) diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index 32ab9dc04..2ce00a10a 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -70,11 +70,11 @@ 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) { - liveSyncCore(); + liveSyncCore(context); } } @@ -92,7 +92,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..85adbbccc 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -18,6 +18,7 @@ export * from "./application-common"; import { createViewFromEntry } from "../ui/builder"; import { ios as iosView, View } from "../ui/core/view"; import { Frame, NavigationEntry } from "../ui/frame"; +import { loadCss } from "../ui/styling/style-scope"; import * as utils from "../utils/utils"; import { profile, level as profilingLevel, Level } from "../profiling"; @@ -225,10 +226,21 @@ class IOSApplication implements IOSApplicationDefinition { } } - public _onLivesync(): void { - // If view can't handle livesync set window controller. - if (!this._rootView._onLivesync()) { - this.setWindowContent(); + public _onLivesync(context?: HmrContext): void { + let executeLivesync = true; + // HMR has context, livesync does not + if (context) { + if (context.module === getCssFileName()) { + loadCss(context.module); + this._rootView._onCssStateChange(); + executeLivesync = false; + } + } + if (executeLivesync) { + // If view can't handle livesync set window controller. + if (!this._rootView._onLivesync()) { + this.setWindowContent(); + } } } @@ -264,8 +276,8 @@ exports.ios = iosApp; setApplication(iosApp); // attach on global, so it can be overwritten in NativeScript Angular -(global).__onLiveSyncCore = function () { - iosApp._onLivesync(); +(global).__onLiveSyncCore = function __onLiveSyncCore(context?: HmrContext) { + iosApp._onLivesync(context); } let mainEntry: NavigationEntry; @@ -373,10 +385,10 @@ function setViewControllerView(view: View): void { } } -global.__onLiveSync = function () { +global.__onLiveSync = function __onLiveSync(context?: HmrContext) { if (!started) { return; } - livesync(); + livesync(context); } diff --git a/tns-core-modules/module.d.ts b/tns-core-modules/module.d.ts index 5fd5d161d..3d973d27f 100644 --- a/tns-core-modules/module.d.ts +++ b/tns-core-modules/module.d.ts @@ -51,8 +51,8 @@ declare namespace NodeJS { __native?: any; __inspector?: any; __extends: any; - __onLiveSync: () => void; - __onLiveSyncCore: () => void; + __onLiveSync: (context?: { type: string, module: string }) => void; + __onLiveSyncCore: (context?: { type: string, module: string }) => void; __onUncaughtError: (error: NativeScriptError) => void; TNS_WEBPACK?: boolean; __requireOverride?: (name: string, dir: string) => any; @@ -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..077d01038 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -17,6 +17,7 @@ import { _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType } from "./fragment.transitions"; +import { loadCss } from "../styling/style-scope"; import { profile } from "../../profiling"; // TODO: Remove this and get it from global to decouple builder for angular @@ -82,13 +83,24 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener { return attachStateChangeListener; } -export function reloadPage(): void { +export function reloadPage(context?: HmrContext): void { const activity = application.android.foregroundActivity; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; const rootView: View = callbacks.getRootView(); - if (!rootView || !rootView._onLivesync()) { - callbacks.resetActivityContent(activity); + let executeLivesync = true; + // HMR has context, livesync does not + if (context) { + if (context.module === application.getCssFileName()) { + loadCss(context.module); + rootView._onCssStateChange(); + executeLivesync = false; + } + } + if (executeLivesync) { + if (!rootView || !rootView._onLivesync()) { + callbacks.resetActivityContent(activity); + } } } @@ -469,19 +481,19 @@ export class Frame extends FrameBase { switch (this.actionBarVisibility) { case "never": return false; - + case "always": return true; - + default: if (page.actionBarHidden !== undefined) { return !page.actionBarHidden; } - + if (this._android && this._android.showActionBar !== undefined) { return this._android.showActionBar; } - + return true; } } @@ -846,14 +858,14 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // parent while its supposed parent believes it properly removed its children; in order to "force" the child to // lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass) const nativeView = page.nativeViewProtected; - if (nativeView != null) { - const parentView = nativeView.getParent(); + if (nativeView != null) { + const parentView = nativeView.getParent(); if (parentView instanceof android.view.ViewGroup) { if (parentView.getChildCount() === 0) { parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); } - parentView.removeView(nativeView); + parentView.removeView(nativeView); } } diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts index 0d85ab1ab..738c0bfa8 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,10 @@ export class PageBase extends ContentView implements PageDefinition { public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) { this._navigationContext = context; + 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..e085c2870 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; } @@ -307,7 +307,7 @@ function onLiveSync(args: applicationCommon.CssChangedEventData): void { loadCss(applicationCommon.getCssFileName()); } -const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { +export const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { if (!cssFile) { return undefined; } @@ -343,6 +343,7 @@ export class CssState { _appliedChangeMap: Readonly>; _appliedPropertyValues: Readonly<{}>; _appliedAnimations: ReadonlyArray; + _appliedSelectorsVersion: number; _match: SelectorsMatch; _matchInvalid: boolean; @@ -367,6 +368,15 @@ export class CssState { } } + public isSelectorsLatestVersionApplied(): boolean { + if (this._appliedSelectorsVersion && this.view._styleScope) { + this.view._styleScope.ensureSelectors(); + return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion; + } else { + return true; + } + } + public onLoaded(): void { if (this._matchInvalid) { this.updateMatch(); @@ -381,6 +391,7 @@ export class CssState { @profile private updateMatch() { + this._appliedSelectorsVersion = this.view._styleScope._getSelectorsVersion(); this._match = this.view._styleScope ? this.view._styleScope.matchSelectors(this.view) : CssState.emptyMatch; this._matchInvalid = false; } @@ -597,8 +608,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 +618,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[][] = []; diff --git a/tsconfig.shared.json b/tsconfig.shared.json index eea37a566..d8fb42fd4 100644 --- a/tsconfig.shared.json +++ b/tsconfig.shared.json @@ -26,4 +26,4 @@ "tns-core-modules/*": ["tns-core-modules/*"] } } -} +} \ No newline at end of file From 790bcfb470f689f79e5892547ce237731d805e28 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Wed, 5 Dec 2018 14:24:24 +0200 Subject: [PATCH 2/5] refactor(HMR): apply changes in application styles at runtime --- .../application/application-common.ts | 24 +++++++++++-- .../application/application.ios.ts | 28 +++++---------- tns-core-modules/module.d.ts | 2 +- tns-core-modules/ui/frame/frame.android.ts | 34 ++++++------------- tns-core-modules/ui/page/page-common.ts | 7 ++-- tns-core-modules/ui/styling/style-scope.ts | 9 ++--- tsconfig.shared.json | 2 +- 7 files changed, 49 insertions(+), 57 deletions(-) diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index 2ce00a10a..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 }; @@ -73,8 +80,19 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v export function livesync(context?: HmrContext) { events.notify({ eventName: "livesync", object: app }); const liveSyncCore = global.__onLiveSyncCore; - if (liveSyncCore) { - liveSyncCore(context); + 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(); } } diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index 85adbbccc..af6028cd4 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -18,7 +18,6 @@ export * from "./application-common"; import { createViewFromEntry } from "../ui/builder"; import { ios as iosView, View } from "../ui/core/view"; import { Frame, NavigationEntry } from "../ui/frame"; -import { loadCss } from "../ui/styling/style-scope"; import * as utils from "../utils/utils"; import { profile, level as profilingLevel, Level } from "../profiling"; @@ -162,7 +161,7 @@ class IOSApplication implements IOSApplicationDefinition { this.setWindowContent(args.root); } else { this._window = UIApplication.sharedApplication.delegate.window; - } + } } @profile @@ -226,21 +225,10 @@ class IOSApplication implements IOSApplicationDefinition { } } - public _onLivesync(context?: HmrContext): void { - let executeLivesync = true; - // HMR has context, livesync does not - if (context) { - if (context.module === getCssFileName()) { - loadCss(context.module); - this._rootView._onCssStateChange(); - executeLivesync = false; - } - } - if (executeLivesync) { - // If view can't handle livesync set window controller. - if (!this._rootView._onLivesync()) { - this.setWindowContent(); - } + public _onLivesync(): void { + // If view can't handle livesync set window controller. + if (!this._rootView._onLivesync()) { + this.setWindowContent(); } } @@ -276,8 +264,8 @@ exports.ios = iosApp; setApplication(iosApp); // attach on global, so it can be overwritten in NativeScript Angular -(global).__onLiveSyncCore = function __onLiveSyncCore(context?: HmrContext) { - iosApp._onLivesync(context); +(global).__onLiveSyncCore = function () { + iosApp._onLivesync(); } let mainEntry: NavigationEntry; @@ -391,4 +379,4 @@ global.__onLiveSync = function __onLiveSync(context?: HmrContext) { } 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 3d973d27f..0a0f6a033 100644 --- a/tns-core-modules/module.d.ts +++ b/tns-core-modules/module.d.ts @@ -52,7 +52,7 @@ declare namespace NodeJS { __inspector?: any; __extends: any; __onLiveSync: (context?: { type: string, module: string }) => void; - __onLiveSyncCore: (context?: { type: string, module: string }) => void; + __onLiveSyncCore: () => void; __onUncaughtError: (error: NativeScriptError) => void; TNS_WEBPACK?: boolean; __requireOverride?: (name: string, dir: string) => any; diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 077d01038..6756bf915 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -17,7 +17,6 @@ import { _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType } from "./fragment.transitions"; -import { loadCss } from "../styling/style-scope"; import { profile } from "../../profiling"; // TODO: Remove this and get it from global to decouple builder for angular @@ -83,24 +82,13 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener { return attachStateChangeListener; } -export function reloadPage(context?: HmrContext): void { +export function reloadPage(): void { const activity = application.android.foregroundActivity; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; const rootView: View = callbacks.getRootView(); - let executeLivesync = true; - // HMR has context, livesync does not - if (context) { - if (context.module === application.getCssFileName()) { - loadCss(context.module); - rootView._onCssStateChange(); - executeLivesync = false; - } - } - if (executeLivesync) { - if (!rootView || !rootView._onLivesync()) { - callbacks.resetActivityContent(activity); - } + if (!rootView || !rootView._onLivesync()) { + callbacks.resetActivityContent(activity); } } @@ -481,19 +469,19 @@ export class Frame extends FrameBase { switch (this.actionBarVisibility) { case "never": return false; - + case "always": return true; - + default: if (page.actionBarHidden !== undefined) { return !page.actionBarHidden; } - + if (this._android && this._android.showActionBar !== undefined) { return this._android.showActionBar; } - + return true; } } @@ -858,14 +846,14 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // parent while its supposed parent believes it properly removed its children; in order to "force" the child to // lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass) const nativeView = page.nativeViewProtected; - if (nativeView != null) { - const parentView = nativeView.getParent(); + if (nativeView != null) { + const parentView = nativeView.getParent(); if (parentView instanceof android.view.ViewGroup) { if (parentView.getChildCount() === 0) { parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); } - parentView.removeView(nativeView); + parentView.removeView(nativeView); } } @@ -1210,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 738c0bfa8..28814e186 100644 --- a/tns-core-modules/ui/page/page-common.ts +++ b/tns-core-modules/ui/page/page-common.ts @@ -103,8 +103,11 @@ export class PageBase extends ContentView implements PageDefinition { public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) { this._navigationContext = context; - if (!this._cssState.isSelectorsLatestVersionApplied()) { - this._onCssStateChange(); + if (isBackNavigation && this._styleScope) { + this._styleScope.ensureSelectors(); + if (!this._cssState.isSelectorsLatestVersionApplied()) { + this._onCssStateChange(); + } } //https://github.com/NativeScript/NativeScript/issues/731 diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index e085c2870..c49bc5e35 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -307,7 +307,7 @@ function onLiveSync(args: applicationCommon.CssChangedEventData): void { loadCss(applicationCommon.getCssFileName()); } -export const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { +const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { if (!cssFile) { return undefined; } @@ -369,12 +369,7 @@ export class CssState { } public isSelectorsLatestVersionApplied(): boolean { - if (this._appliedSelectorsVersion && this.view._styleScope) { - this.view._styleScope.ensureSelectors(); - return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion; - } else { - return true; - } + return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion; } public onLoaded(): void { diff --git a/tsconfig.shared.json b/tsconfig.shared.json index d8fb42fd4..eea37a566 100644 --- a/tsconfig.shared.json +++ b/tsconfig.shared.json @@ -26,4 +26,4 @@ "tns-core-modules/*": ["tns-core-modules/*"] } } -} \ No newline at end of file +} From 4c15f717898a5764631211ad59bbe9f9197fdc12 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 6 Dec 2018 14:57:15 +0200 Subject: [PATCH 3/5] fix: iOS tests --- tns-core-modules/ui/styling/style-scope.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index c49bc5e35..a915b559d 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -386,8 +386,12 @@ export class CssState { @profile private updateMatch() { - this._appliedSelectorsVersion = this.view._styleScope._getSelectorsVersion(); - 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; } From b9d7d6bb6294c0b658b7c5a833a9d3197530ba61 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 13 Dec 2018 07:34:37 +0200 Subject: [PATCH 4/5] test(HMR): apply changes in application styles at runtime --- tests/app/app/app-new.css | 3 + tests/app/app/app-new.scss | 3 + tests/app/app/application.css | 3 + tests/app/app/mainPage.ts | 8 +- tests/app/app/mainPage.xml | 2 +- tests/app/livesync/livesync-tests.ts | 120 +++++++++++++++++++++++++++ tests/app/testRunner.ts | 9 +- 7 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 tests/app/app/app-new.css create mode 100644 tests/app/app/app-new.scss create mode 100644 tests/app/app/application.css create mode 100644 tests/app/livesync/livesync-tests.ts 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..8916d8338 --- /dev/null +++ b/tests/app/livesync/livesync-tests.ts @@ -0,0 +1,120 @@ +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 { Button } from "tns-core-modules/ui/button/button"; +import { Color } from "tns-core-modules/color"; +import { Page } from "tns-core-modules/ui/page"; +import { Label } from "tns-core-modules/ui/label/label"; +import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout"; + +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 mainPageFactory = function (): Page { + const page = new Page(); + const stack = new StackLayout(); + const label = new Label(); + label.id = "label"; + label.text = "label"; + stack.addChild(label); + page.content = stack; + return page; +} + +const pageFactory = function (): Page { + const page = new Page(); + const stack = new StackLayout(); + const button = new Button(); + button.id = "button"; + button.text = "button"; + stack.addChild(button); + page.content = stack; + return page; +} + +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() { + helper.navigate(mainPageFactory); +} + +export function tearDown() { + app.setCssFileName(appCssFileName); +} + +function _test_onLiveSync_HmrContext_AppStyle(styleFileName: string) { + const pageBeforeNavigation = helper.getCurrentPage(); + + helper.navigateWithHistory(pageFactory); + 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 }) { + helper.navigateWithHistory(pageFactory); + 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; From c404a3803861615c533dcd54f09498808addbd4d Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 13 Dec 2018 17:19:23 +0200 Subject: [PATCH 5/5] refactor: tests to parce templates --- tests/app/livesync/livesync-tests.ts | 45 ++++++++++++---------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/tests/app/livesync/livesync-tests.ts b/tests/app/livesync/livesync-tests.ts index 8916d8338..8ae772203 100644 --- a/tests/app/livesync/livesync-tests.ts +++ b/tests/app/livesync/livesync-tests.ts @@ -2,11 +2,9 @@ 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 { Button } from "tns-core-modules/ui/button/button"; import { Color } from "tns-core-modules/color"; +import { parse } from "tns-core-modules/ui/builder"; import { Page } from "tns-core-modules/ui/page"; -import { Label } from "tns-core-modules/ui/label/label"; -import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout"; const appCssFileName = "./app/application.css"; const appNewCssFileName = "./app/app-new.css"; @@ -19,27 +17,19 @@ const mainPageXmlFileName = "./app/main-page.xml"; const green = new Color("green"); -const mainPageFactory = function (): Page { - const page = new Page(); - const stack = new StackLayout(); - const label = new Label(); - label.id = "label"; - label.text = "label"; - stack.addChild(label); - page.content = stack; - return page; -} +const mainPageTemplate = ` + + + + + `; -const pageFactory = function (): Page { - const page = new Page(); - const stack = new StackLayout(); - const button = new Button(); - button.id = "button"; - button.text = "button"; - stack.addChild(button); - page.content = stack; - return page; -} +const pageTemplate = ` + + + + + `; export function test_onLiveSync_HmrContext_AppStyle_AppNewCss() { _test_onLiveSync_HmrContext_AppStyle(appNewCssFileName); @@ -78,7 +68,8 @@ export function test_onLiveSync_HmrContext_Markup_MainPageXml() { } export function setUpModule() { - helper.navigate(mainPageFactory); + const mainPage = parse(mainPageTemplate); + helper.navigate(() => mainPage); } export function tearDown() { @@ -88,7 +79,8 @@ export function tearDown() { function _test_onLiveSync_HmrContext_AppStyle(styleFileName: string) { const pageBeforeNavigation = helper.getCurrentPage(); - helper.navigateWithHistory(pageFactory); + const page = parse(pageTemplate); + helper.navigateWithHistory(() => page); app.setCssFileName(styleFileName); const pageBeforeLiveSync = helper.getCurrentPage(); @@ -110,7 +102,8 @@ function _test_onLiveSync_HmrContext_AppStyle(styleFileName: string) { } function _test_onLiveSync_HmrContext(context: { type, module }) { - helper.navigateWithHistory(pageFactory); + const page = parse(pageTemplate); + helper.navigateWithHistory(() => page); global.__onLiveSync({ type: context.type, module: context.module }); TKUnit.waitUntilReady(() => !!frame.topmost());