diff --git a/apps/.vscode/launch.json b/apps/.vscode/launch.json new file mode 100644 index 000000000..d2ea7615d --- /dev/null +++ b/apps/.vscode/launch.json @@ -0,0 +1,85 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch on iOS Device", + "type": "nativescript", + "platform": "ios", + "request": "launch", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": false + }, + { + "name": "Attach on iOS Device", + "type": "nativescript", + "platform": "ios", + "request": "attach", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": false + }, + { + "name": "Launch on iOS Emulator", + "type": "nativescript", + "platform": "ios", + "request": "launch", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": true + }, + { + "name": "Attach on iOS Emulator", + "type": "nativescript", + "platform": "ios", + "request": "attach", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": true + }, + { + "name": "Launch on Android Device", + "type": "nativescript", + "platform": "android", + "request": "launch", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": false + }, + { + "name": "Launch on Android Emulator", + "type": "nativescript", + "platform": "android", + "request": "launch", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": true + }, + { + "name": "Attach on Android Device", + "type": "nativescript", + "platform": "android", + "request": "attach", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": false + }, + { + "name": "Attach on Android Emulator", + "type": "nativescript", + "platform": "android", + "request": "attach", + "appRoot": "${workspaceRoot}", + "sourceMaps": true, + "diagnosticLogging": false, + "emulator": true + } + ] +} \ No newline at end of file diff --git a/tests/app/ui/button/button-tests.ts b/tests/app/ui/button/button-tests.ts index aa7ad9948..57b7b03b7 100644 --- a/tests/app/ui/button/button-tests.ts +++ b/tests/app/ui/button/button-tests.ts @@ -300,4 +300,29 @@ export var test_WhenFormattedTextPropertyChanges_TextIsUpdated_Button = function view.formattedText = null; TKUnit.assertEqual(view.text, ""); }); +} + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithoutFormattedText_DoesNotCrash() { + let view = new buttonModule.Button(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.text = "NormalText"; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithFormattedText_DoesNotCrash() { + let view = new buttonModule.Button(); + let formattedString = helper._generateFormattedString(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.formattedText = formattedString; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); } \ No newline at end of file diff --git a/tests/app/ui/helper.ts b/tests/app/ui/helper.ts index dc935d79a..4a0f1b238 100644 --- a/tests/app/ui/helper.ts +++ b/tests/app/ui/helper.ts @@ -8,6 +8,10 @@ import utils = require("utils/utils"); import types = require("utils/types"); import styling = require("ui/styling"); import platform = require("platform"); +import colorModule = require("color"); +import formattedStringModule = require("text/formatted-string"); +import spanModule = require("text/span"); +import enums = require("ui/enums"); var DELTA = 0.1; @@ -201,4 +205,33 @@ export function forceGC() { } utils.GC(); +} + +export function _generateFormattedString(): formattedStringModule.FormattedString{ + let formattedString = new formattedStringModule.FormattedString(); + let span: spanModule.Span; + + span = new spanModule.Span(); + span.fontFamily = "serif"; + span.fontSize = 10; + span.fontAttributes = enums.FontAttributes.Bold; + span.foregroundColor = new colorModule.Color("red"); + span.backgroundColor = new colorModule.Color("blue"); + span.underline = 0; + span.strikethrough = 1; + span.text = "Formatted"; + formattedString.spans.push(span); + + span = new spanModule.Span(); + span.fontFamily = "sans-serif"; + span.fontSize = 20; + span.fontAttributes = enums.FontAttributes.Italic; + span.foregroundColor = new colorModule.Color("green"); + span.backgroundColor = new colorModule.Color("yellow"); + span.underline = 1; + span.strikethrough = 0; + span.text = "Text"; + formattedString.spans.push(span); + + return formattedString; } \ No newline at end of file diff --git a/tests/app/ui/label/label-tests.ts b/tests/app/ui/label/label-tests.ts index 09e4bfbf7..e7af3a672 100644 --- a/tests/app/ui/label/label-tests.ts +++ b/tests/app/ui/label/label-tests.ts @@ -22,6 +22,7 @@ import {isIOS} from "platform"; import {Label} from "ui/label"; import {LayoutBase} from "ui/layouts/layout-base"; import * as helper from "../helper"; +import viewModule = require("ui/core/view"); export class LabelTest extends testModule.UITest { @@ -596,3 +597,27 @@ export function createTestCase(): LabelTest { return new LabelTest(); } +export function test_IntegrationTest_Transform_Decoration_Spacing_WithoutFormattedText_DoesNotCrash() { + let view = new LabelModule.Label(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.text = "NormalText"; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithFormattedText_DoesNotCrash() { + let view = new LabelModule.Label(); + let formattedString = helper._generateFormattedString(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.formattedText = formattedString; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} \ No newline at end of file diff --git a/tests/app/ui/text-field/text-field-tests.ts b/tests/app/ui/text-field/text-field-tests.ts index 85080ba31..60288957b 100644 --- a/tests/app/ui/text-field/text-field-tests.ts +++ b/tests/app/ui/text-field/text-field-tests.ts @@ -535,3 +535,33 @@ export var test_WhenFormattedTextPropertyChanges_TextIsUpdated_TextBase = functi TKUnit.assertEqual(view.text, ""); }); } + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithoutFormattedText_DoesNotCrash() { + let view = new textFieldModule.TextField(); + helper.buildUIAndRunTest(view, function (views: Array) { + TKUnit.assertEqual(view.text, "", "Text"); + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.none, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.none, "TextDecoration"); + TKUnit.assertTrue(isNaN(view.style.letterSpacing), "LetterSpacing"); + + view.text = "NormalText"; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithFormattedText_DoesNotCrash() { + let view = new textFieldModule.TextField(); + let formattedString = helper._generateFormattedString(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.formattedText = formattedString; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} \ No newline at end of file diff --git a/tests/app/ui/text-view/text-view-tests.ts b/tests/app/ui/text-view/text-view-tests.ts index f03186e0f..afec52c01 100644 --- a/tests/app/ui/text-view/text-view-tests.ts +++ b/tests/app/ui/text-view/text-view-tests.ts @@ -494,3 +494,28 @@ export function test_watch_listerer_is_removed_at_onDetach() { }); } } + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithoutFormattedText_DoesNotCrash() { + let view = new textViewModule.TextView(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.text = "NormalText"; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} + +export function test_IntegrationTest_Transform_Decoration_Spacing_WithFormattedText_DoesNotCrash() { + let view = new textViewModule.TextView(); + let formattedString = helper._generateFormattedString(); + helper.buildUIAndRunTest(view, function (views: Array) { + view.formattedText = formattedString; + view.setInlineStyle("text-transform: uppercase; text-decoration: underline; letter-spacing: 10;"); + + TKUnit.assertEqual(view.style.textTransform, enums.TextTransform.uppercase, "TextTransform"); + TKUnit.assertEqual(view.style.textDecoration, enums.TextDecoration.underline, "TextDecoration"); + TKUnit.assertEqual(view.style.letterSpacing, 10, "LetterSpacing"); + }); +} \ No newline at end of file diff --git a/tns-core-modules/text/formatted-string-common.ts b/tns-core-modules/text/formatted-string-common.ts index 9a7d6c225..6ecc51c36 100644 --- a/tns-core-modules/text/formatted-string-common.ts +++ b/tns-core-modules/text/formatted-string-common.ts @@ -234,4 +234,8 @@ export class FormattedString extends observable.Observable implements definition view.formattedText = value; } } + + public _updateCharactersInRangeReplacementString(rangeLocation: number, rangeLength: number, replacementString: string): void { + // + } } \ No newline at end of file diff --git a/tns-core-modules/text/formatted-string.d.ts b/tns-core-modules/text/formatted-string.d.ts index f6711c365..8c0c323ca 100644 --- a/tns-core-modules/text/formatted-string.d.ts +++ b/tns-core-modules/text/formatted-string.d.ts @@ -97,6 +97,11 @@ declare module "text/formatted-string" { /** * A static method used to add child elements of the FormattedString class to a View declared in xml. */ - public static addFormattedStringToView(view: FormattedStringView, name: string, value: any): void; + public static addFormattedStringToView(view: FormattedStringView, name: string, value: any): void; + + //@private + createFormattedStringCore(): void; + _updateCharactersInRangeReplacementString(rangeLocation: number, rangeLength: number, replacementString: string): void; + //@endprivate } } \ No newline at end of file diff --git a/tns-core-modules/text/formatted-string.ios.ts b/tns-core-modules/text/formatted-string.ios.ts index cde590c76..7f012017b 100644 --- a/tns-core-modules/text/formatted-string.ios.ts +++ b/tns-core-modules/text/formatted-string.ios.ts @@ -6,25 +6,49 @@ global.moduleMerge(formattedStringCommon, exports); export class FormattedString extends formattedStringCommon.FormattedString { public createFormattedStringCore() { - var mas = NSMutableAttributedString.alloc().init(); - var i; - var spanStart = 0; - var spanLength = 0; - var spanText = ""; - for (i = 0; i < this.spans.length; i++) { - var span = this.spans.getItem(i); + let mas = NSMutableAttributedString.alloc().init(); + let spanStart = 0; + let spanLength = 0; + let spanText = ""; + for (let i = 0; i < this.spans.length; i++) { + let span = this.spans.getItem(i); spanText = types.toUIString(span.text); spanLength = spanText.length; span.updateSpanModifiers(this); - var attrDict = NSMutableDictionary.alloc().init(); - var p; - for (p = 0; p < span.spanModifiers.length; p++) { + let attrDict = NSMutableDictionary.alloc().init(); + for (let p = 0; p < span.spanModifiers.length; p++) { attrDict.setObjectForKey(span.spanModifiers[p].value, span.spanModifiers[p].key); } - var nsAttributedString = NSMutableAttributedString.alloc().initWithStringAttributes(String(spanText), attrDict); + let nsAttributedString = NSMutableAttributedString.alloc().initWithStringAttributes(String(spanText), attrDict); mas.insertAttributedStringAtIndex(nsAttributedString, spanStart); spanStart += spanLength; } this._formattedText = mas; } + + public _updateCharactersInRangeReplacementString(rangeLocation: number, rangeLength: number, replacementString: string): void { + let deletingText = !replacementString; + let currentLocation = 0; + for (let i = 0; i < this.spans.length; i++) { + let span = this.spans.getItem(i); + if (currentLocation <= rangeLocation && rangeLocation < (currentLocation + span.text.length)){ + let newText = splice(span.text, rangeLocation - currentLocation, deletingText ? rangeLength : 0, replacementString); + span._setTextInternal(newText); + //console.log(`>>> ${span.text}`); + return; + } + currentLocation += span.text.length; + } + } } + +/* + * @param {String} value The string to splice. + * @param {number} start Index at which to start changing the string. + * @param {number} delCount An integer indicating the number of old chars to remove. + * @param {string} newSubStr The String that is spliced in. + * @return {string} A new string with the spliced substring.function splice(value: string, start: number, delCount: number, newSubStr: string) { + */ +function splice(value: string, start: number, delCount: number, newSubStr: string) { + return value.slice(0, start) + newSubStr + value.slice(start + Math.abs(delCount)); +}; \ No newline at end of file diff --git a/tns-core-modules/text/span-common.ts b/tns-core-modules/text/span-common.ts index 768b8b566..94a8d1c85 100644 --- a/tns-core-modules/text/span-common.ts +++ b/tns-core-modules/text/span-common.ts @@ -140,11 +140,15 @@ export class Span extends bindable.Bindable implements definition.Span, view.App set text(value: string) { if (this._text !== value) { - this._text = value; + this._setTextInternal(value); this.updateAndNotify(); } } + _setTextInternal(value: string): void { + this._text = value; + } + get parentFormattedString(): formattedString.FormattedString { return this._parentFormattedString; } diff --git a/tns-core-modules/text/span.d.ts b/tns-core-modules/text/span.d.ts index f6924440e..e09dd4900 100644 --- a/tns-core-modules/text/span.d.ts +++ b/tns-core-modules/text/span.d.ts @@ -72,5 +72,10 @@ * Ends the process previously initiated by beginEdit and updates the span modifiers collection. */ public endEdit(): void; + + //@private + _setTextInternal(value: string): void; + //@endprivate + } } \ No newline at end of file diff --git a/tns-core-modules/ui/button/button-common.ts b/tns-core-modules/ui/button/button-common.ts index edf7dd924..4657e23ed 100644 --- a/tns-core-modules/ui/button/button-common.ts +++ b/tns-core-modules/ui/button/button-common.ts @@ -28,6 +28,7 @@ function onTextPropertyChanged(data: PropertyChangeData) { button._onTextPropertyChanged(data); + //RemoveThisDoubleCall button.style._updateTextDecoration(); button.style._updateTextTransform(); } @@ -53,6 +54,8 @@ export class Button extends view.View implements definition.Button { this.formattedText.updateSpansBindingContext(newValue); } + //This is because of ListView virtualization + //RemoveThisDoubleCall this.style._updateTextDecoration(); this.style._updateTextTransform(); } diff --git a/tns-core-modules/ui/button/button.ios.ts b/tns-core-modules/ui/button/button.ios.ts index a1ecb7c54..3ff0fa1eb 100644 --- a/tns-core-modules/ui/button/button.ios.ts +++ b/tns-core-modules/ui/button/button.ios.ts @@ -6,6 +6,7 @@ import view = require("ui/core/view"); import utils = require("utils/utils"); import enums = require("ui/enums"); import dependencyObservable = require("ui/core/dependency-observable"); +import types = require("utils/types"); import {PseudoClassHandler} from "ui/core/view"; class TapHandlerImpl extends NSObject { @@ -63,7 +64,9 @@ export class Button extends common.Button { // set the value for the normal state. let newText = value ? value._formattedText : null; this.ios.setAttributedTitleForState(newText, UIControlState.UIControlStateNormal); + //RemoveThisDoubleCall this.style._updateTextDecoration(); + this.style._updateTextTransform(); } @PseudoClassHandler("normal", "highlighted") @@ -164,29 +167,29 @@ export class ButtonStyler implements style.Styler { // text-decoration private static setTextDecorationProperty(view: view.View, newValue: any) { - utils.ios.setTextDecorationAndTransform(view, newValue, view.style.textTransform, view.style.letterSpacing); + ButtonStyler._setButtonTextDecorationAndTransform(