diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index bc8411da2fb..a485b26a0e2 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -121,7 +121,7 @@ define(function (require, exports, module) { */ function _addRule(selectorName, inlineEditor, path) { DocumentManager.getDocumentForPath(path).done(function (styleDoc) { - var newRuleInfo = CSSUtils.addRuleToDocument(styleDoc, selectorName, Editor.getUseTabChar(), Editor.getSpaceUnits()); + var newRuleInfo = CSSUtils.addRuleToDocument(styleDoc, selectorName, Editor.getUseTabChar(path), Editor.getSpaceUnits(path)); inlineEditor.addAndSelectRange(selectorName, styleDoc, newRuleInfo.range.from.line, newRuleInfo.range.to.line); inlineEditor.editor.setCursorPos(newRuleInfo.pos.line, newRuleInfo.pos.ch); }); diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 0f6fc376b67..93f9ee98afa 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -74,6 +74,7 @@ define(function (require, exports, module) { Strings = require("strings"), TextRange = require("document/TextRange").TextRange, TokenUtils = require("utils/TokenUtils"), + ValidationUtils = require("utils/ValidationUtils"), ViewUtils = require("utils/ViewUtils"), _ = require("thirdparty/lodash"); @@ -92,6 +93,14 @@ define(function (require, exports, module) { var cmOptions = {}; + /** @type {number} Constants */ + var MIN_SPACE_UNITS = 0, + MIN_TAB_SIZE = 1, + DEFAULT_SPACE_UNITS = 4, + DEFAULT_TAB_SIZE = 4, + MAX_SPACE_UNITS = 10, + MAX_TAB_SIZE = 10; + // Mappings from Brackets preferences to CodeMirror options cmOptions[CLOSE_BRACKETS] = "autoCloseBrackets"; cmOptions[CLOSE_TAGS] = "autoCloseTags"; @@ -110,9 +119,13 @@ define(function (require, exports, module) { PreferencesManager.definePreference(SHOW_LINE_NUMBERS, "boolean", true); PreferencesManager.definePreference(SMART_INDENT, "boolean", true); PreferencesManager.definePreference(SOFT_TABS, "boolean", true); - PreferencesManager.definePreference(SPACE_UNITS, "number", 4); + PreferencesManager.definePreference(SPACE_UNITS, "number", DEFAULT_SPACE_UNITS, { + validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_SPACE_UNITS, MAX_SPACE_UNITS) + }); PreferencesManager.definePreference(STYLE_ACTIVE_LINE, "boolean", false); - PreferencesManager.definePreference(TAB_SIZE, "number", 4); + PreferencesManager.definePreference(TAB_SIZE, "number", DEFAULT_TAB_SIZE, { + validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_TAB_SIZE, MAX_TAB_SIZE) + }); PreferencesManager.definePreference(USE_TAB_CHAR, "boolean", false); PreferencesManager.definePreference(WORD_WRAP, "boolean", true); @@ -2022,100 +2035,149 @@ define(function (require, exports, module) { * Sets whether to use tab characters (vs. spaces) when inserting new text. * Affects any editors that share the same preference location. * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setUseTabChar = function (value) { - PreferencesManager.set(USE_TAB_CHAR, value); + Editor.setUseTabChar = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(USE_TAB_CHAR, value, options); }; - /** @type {boolean} Gets whether the current editor uses tab characters (vs. spaces) when inserting new text */ - Editor.getUseTabChar = function () { - return PreferencesManager.get(USE_TAB_CHAR); + /** + * Gets whether the specified or current file uses tab characters (vs. spaces) when inserting new text + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} + */ + Editor.getUseTabChar = function (fullPath) { + return PreferencesManager.get(USE_TAB_CHAR, fullPath); }; /** * Sets tab character width. * Affects any editors that share the same preference location. * @param {number} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setTabSize = function (value) { - PreferencesManager.set(TAB_SIZE, value); + Editor.setTabSize = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(TAB_SIZE, value, options); }; - /** @type {number} Get indent unit */ - Editor.getTabSize = function () { - return PreferencesManager.get(TAB_SIZE); + /** + * Get indent unit + * @param {string=} fullPath Path to file to get preference for + * @return {number} + */ + Editor.getTabSize = function (fullPath) { + return PreferencesManager.get(TAB_SIZE, fullPath); }; /** * Sets indentation width. * Affects any editors that share the same preference location. * @param {number} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setSpaceUnits = function (value) { - PreferencesManager.set(SPACE_UNITS, value); + Editor.setSpaceUnits = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(SPACE_UNITS, value, options); }; - /** @type {number} Get indentation width */ - Editor.getSpaceUnits = function () { - return PreferencesManager.get(SPACE_UNITS); + /** + * Get indentation width + * @param {string=} fullPath Path to file to get preference for + * @return {number} + */ + Editor.getSpaceUnits = function (fullPath) { + return PreferencesManager.get(SPACE_UNITS, fullPath); }; /** * Sets the auto close brackets. * Affects any editors that share the same preference location. * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setCloseBrackets = function (value) { - PreferencesManager.set(CLOSE_BRACKETS, value); + Editor.setCloseBrackets = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(CLOSE_BRACKETS, value, options); }; - /** @type {boolean} Gets whether the current editor uses auto close brackets */ - Editor.getCloseBrackets = function () { - return PreferencesManager.get(CLOSE_BRACKETS); + /** + * Gets whether the specified or current file uses auto close brackets + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} + */ + Editor.getCloseBrackets = function (fullPath) { + return PreferencesManager.get(CLOSE_BRACKETS, fullPath); }; /** * Sets show line numbers option. * Affects any editors that share the same preference location. * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setShowLineNumbers = function (value) { - PreferencesManager.set(SHOW_LINE_NUMBERS, value); + Editor.setShowLineNumbers = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(SHOW_LINE_NUMBERS, value, options); }; - /** @type {boolean} Returns true if show line numbers is enabled for the current editor */ - Editor.getShowLineNumbers = function () { - return PreferencesManager.get(SHOW_LINE_NUMBERS); + /** + * Returns true if show line numbers is enabled for the specified or current file + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} + */ + Editor.getShowLineNumbers = function (fullPath) { + return PreferencesManager.get(SHOW_LINE_NUMBERS, fullPath); }; /** * Sets show active line option. * Affects any editors that share the same preference location. * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setShowActiveLine = function (value) { - PreferencesManager.set(STYLE_ACTIVE_LINE, value); + Editor.setShowActiveLine = function (value, fullPath) { + return PreferencesManager.set(STYLE_ACTIVE_LINE, value); }; - /** @type {boolean} Returns true if show active line is enabled for the current editor */ - Editor.getShowActiveLine = function () { - return PreferencesManager.get(STYLE_ACTIVE_LINE); + /** + * Returns true if show active line is enabled for the specified or current file + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} + */ + Editor.getShowActiveLine = function (fullPath) { + return PreferencesManager.get(STYLE_ACTIVE_LINE, fullPath); }; /** * Sets word wrap option. * Affects any editors that share the same preference location. * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid */ - Editor.setWordWrap = function (value) { - PreferencesManager.set(WORD_WRAP, value); + Editor.setWordWrap = function (value, fullPath) { + var options = fullPath && {context: fullPath}; + return PreferencesManager.set(WORD_WRAP, value, options); }; - /** @type {boolean} Returns true if word wrap is enabled for the current editor */ - Editor.getWordWrap = function () { - return PreferencesManager.get(WORD_WRAP); + /** + * Returns true if word wrap is enabled for the specified or current file + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} + */ + Editor.getWordWrap = function (fullPath) { + return PreferencesManager.get(WORD_WRAP, fullPath); }; + // Set up listeners for preference changes editorOptions.forEach(function (prefName) { PreferencesManager.on("change", prefName, function () { diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index d4c9151e953..b80112df067 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -51,42 +51,81 @@ define(function (require, exports, module) { $statusOverwrite; + /** + * Determine string based on count + * @param {number} number Count + * @param {string} singularStr Singular string + * @param {string} pluralStr Plural string + * @return {string} Proper string to use for count + */ function _formatCountable(number, singularStr, pluralStr) { return StringUtils.format(number > 1 ? pluralStr : singularStr, number); } + /** + * Update file mode + * @param {Editor} editor Current editor + */ function _updateLanguageInfo(editor) { $languageInfo.text(editor.document.getLanguage().getName()); } + /** + * Update file information + * @param {Editor} editor Current editor + */ function _updateFileInfo(editor) { var lines = editor.lineCount(); $fileInfo.text(_formatCountable(lines, Strings.STATUSBAR_LINE_COUNT_SINGULAR, Strings.STATUSBAR_LINE_COUNT_PLURAL)); } - function _updateIndentType() { - var indentWithTabs = Editor.getUseTabChar(); + /** + * Update indent type and size + * @param {string} fullPath Path to file in current editor + */ + function _updateIndentType(fullPath) { + var indentWithTabs = Editor.getUseTabChar(fullPath); $indentType.text(indentWithTabs ? Strings.STATUSBAR_TAB_SIZE : Strings.STATUSBAR_SPACES); $indentType.attr("title", indentWithTabs ? Strings.STATUSBAR_INDENT_TOOLTIP_SPACES : Strings.STATUSBAR_INDENT_TOOLTIP_TABS); $indentWidthLabel.attr("title", indentWithTabs ? Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_TABS : Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES); } - function _getIndentSize() { - return Editor.getUseTabChar() ? Editor.getTabSize() : Editor.getSpaceUnits(); + /** + * Get indent size based on type + * @param {string} fullPath Path to file in current editor + * @return {number} Indent size + */ + function _getIndentSize(fullPath) { + return Editor.getUseTabChar(fullPath) ? Editor.getTabSize(fullPath) : Editor.getSpaceUnits(fullPath); } - function _updateIndentSize() { - var size = _getIndentSize(); + /** + * Update indent size + * @param {string} fullPath Path to file in current editor + */ + function _updateIndentSize(fullPath) { + var size = _getIndentSize(fullPath); $indentWidthLabel.text(size); $indentWidthInput.val(size); } + /** + * Toggle indent type + */ function _toggleIndentType() { - Editor.setUseTabChar(!Editor.getUseTabChar()); - _updateIndentType(); - _updateIndentSize(); + var current = EditorManager.getActiveEditor(), + fullPath = current && current.document.file.fullPath; + + Editor.setUseTabChar(!Editor.getUseTabChar(fullPath), fullPath); + _updateIndentType(fullPath); + _updateIndentSize(fullPath); } + /** + * Update cursor(s)/selection(s) information + * @param {Event} event (unused) + * @param {Editor} editor Current editor + */ function _updateCursorInfo(event, editor) { editor = editor || EditorManager.getActiveEditor(); @@ -116,7 +155,12 @@ define(function (require, exports, module) { $cursorInfo.text(cursorStr + selStr); } - function _changeIndentWidth(value) { + /** + * Change indent size + * @param {string} fullPath Path to file in current editor + * @param {string} value Size entered into status bar + */ + function _changeIndentWidth(fullPath, value) { $indentWidthLabel.removeClass("hidden"); $indentWidthInput.addClass("hidden"); @@ -126,24 +170,31 @@ define(function (require, exports, module) { // restore focus to the editor EditorManager.focusEditor(); - if (!value || isNaN(value)) { - return; - } - - value = Math.max(Math.min(Math.floor(value), 10), 1); - if (Editor.getUseTabChar()) { - Editor.setTabSize(value); + var valInt = parseInt(value, 10); + if (Editor.getUseTabChar(fullPath)) { + if (!Editor.setTabSize(valInt, fullPath)) { + return; // validation failed + } } else { - Editor.setSpaceUnits(value); + if (!Editor.setSpaceUnits(valInt, fullPath)) { + return; // validation failed + } } // update indicator - _updateIndentSize(); + _updateIndentSize(fullPath); // column position may change when tab size changes _updateCursorInfo(); } + /** + * Update insert/overwrite label + * @param {Event} event (unused) + * @param {Editor} editor Current editor + * @param {string} newstate New overwrite state + * @param {boolean=} doNotAnimate True if state should not be animated + */ function _updateOverwriteLabel(event, editor, newstate, doNotAnimate) { if ($statusOverwrite.text() === (newstate ? Strings.STATUSBAR_OVERWRITE : Strings.STATUSBAR_INSERT)) { // label already up-to-date @@ -157,6 +208,10 @@ define(function (require, exports, module) { } } + /** + * Update insert/overwrite indicator + * @param {Event} event (unused) + */ function _updateEditorOverwriteMode(event) { var editor = EditorManager.getActiveEditor(), newstate = !editor._codeMirror.state.overwrite; @@ -166,10 +221,20 @@ define(function (require, exports, module) { editor.toggleOverwrite(newstate); } + /** + * Initialize insert/overwrite indicator + * @param {Editor} currentEditor Current editor + */ function _initOverwriteMode(currentEditor) { currentEditor.toggleOverwrite($statusOverwrite.text() === Strings.STATUSBAR_OVERWRITE); } + /** + * Handle active editor change event + * @param {Event} event (unused) + * @param {Editor} current Current editor + * @param {Editor} previous Previous editor + */ function _onActiveEditorChange(event, current, previous) { if (previous) { $(previous).off(".statusbar"); @@ -180,12 +245,13 @@ define(function (require, exports, module) { if (!current) { StatusBar.hide(); // calls resizeEditor() if needed } else { + var fullPath = current.document.file.fullPath; StatusBar.show(); // calls resizeEditor() if needed $(current).on("cursorActivity.statusbar", _updateCursorInfo); $(current).on("optionChange.statusbar", function () { - _updateIndentType(); - _updateIndentSize(); + _updateIndentType(fullPath); + _updateIndentSize(fullPath); }); $(current).on("change.statusbar", function () { // async update to keep typing speed smooth @@ -200,11 +266,14 @@ define(function (require, exports, module) { _updateLanguageInfo(current); _updateFileInfo(current); _initOverwriteMode(current); - _updateIndentType(); - _updateIndentSize(); + _updateIndentType(fullPath); + _updateIndentSize(fullPath); } } + /** + * Initialize + */ function _init() { $languageInfo = $("#status-language"); $cursorInfo = $("#status-cursor"); @@ -219,7 +288,8 @@ define(function (require, exports, module) { $indentWidthLabel .on("click", function () { // update the input value before displaying - $indentWidthInput.val(_getIndentSize()); + var current = EditorManager.getActiveEditor(); + $indentWidthInput.val(_getIndentSize(current)); $indentWidthLabel.addClass("hidden"); $indentWidthInput.removeClass("hidden"); @@ -227,13 +297,13 @@ define(function (require, exports, module) { $indentWidthInput .on("blur", function () { - _changeIndentWidth($indentWidthInput.val()); + _changeIndentWidth(current, $indentWidthInput.val()); }) .on("keyup", function (event) { if (event.keyCode === KeyEvent.DOM_VK_RETURN) { $indentWidthInput.blur(); } else if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { - _changeIndentWidth(false); + _changeIndentWidth(current, false); } }); }); diff --git a/src/extensions/default/JSLint/main.js b/src/extensions/default/JSLint/main.js index 90c4f24c3cd..c3ec9705dcc 100644 --- a/src/extensions/default/JSLint/main.js +++ b/src/extensions/default/JSLint/main.js @@ -35,6 +35,7 @@ define(function (require, exports, module) { // Load dependent modules var CodeInspection = brackets.getModule("language/CodeInspection"), + Editor = brackets.getModule("editor/Editor").Editor, PreferencesManager = brackets.getModule("preferences/PreferencesManager"), Strings = brackets.getModule("strings"), _ = brackets.getModule("thirdparty/lodash"); @@ -62,10 +63,7 @@ define(function (require, exports, module) { // gets indentation size depending whether the tabs or spaces are used function _getIndentSize(fullPath) { - return PreferencesManager.get( - PreferencesManager.get("useTabChar", fullPath) ? "tabSize" : "spaceUnits", - fullPath - ); + return Editor.getUseTabChar(fullPath) ? Editor.getTabSize(fullPath) : Editor.getSpaceUnits(fullPath); } /** diff --git a/src/preferences/PreferencesBase.js b/src/preferences/PreferencesBase.js index 14e9ce3178c..21449ddcdb3 100644 --- a/src/preferences/PreferencesBase.js +++ b/src/preferences/PreferencesBase.js @@ -1107,7 +1107,8 @@ define(function (require, exports, module) { type: type, initial: initial, name: options.name, - description: options.description + description: options.description, + validator: options.validator }); this.set(id, initial, { location: { @@ -1436,7 +1437,11 @@ define(function (require, exports, module) { if (scope) { var result = scope.get(id, context); if (result !== undefined) { - return _.cloneDeep(result); + var pref = this.getPreference(id), + validator = pref && pref.validator; + if (!validator || validator(result)) { + return _.cloneDeep(result); + } } } } @@ -1480,7 +1485,8 @@ define(function (require, exports, module) { * @param {string} id Identifier of the preference to set * @param {Object} value New value for the preference * @param {{location: ?Object, context: ?Object}=} options Specific location in which to set the value or the context to use when setting the value - * @return {boolean} true if a value was set + * @return {valid: {boolean}, true if no validator specified or if value is valid + * stored: {boolean}} true if a value was stored */ set: function (id, value, options) { options = options || {}; @@ -1502,12 +1508,19 @@ define(function (require, exports, module) { scope: scopeOrder[scopeOrder.length - 2] }; } else { - return false; + return { valid: true, stored: false }; } } + var scope = this._scopes[location.scope]; if (!scope) { - return false; + return { valid: true, stored: false }; + } + + var pref = this.getPreference(id), + validator = pref && pref.validator; + if (validator && !validator(value)) { + return { valid: false, stored: false }; } var wasSet = scope.set(id, value, context, location); @@ -1516,7 +1529,7 @@ define(function (require, exports, module) { ids: [id] }); } - return wasSet; + return { valid: true, stored: wasSet }; }, /** diff --git a/src/preferences/PreferencesManager.js b/src/preferences/PreferencesManager.js index 7ddb558bbc1..6d3b8c94c0a 100644 --- a/src/preferences/PreferencesManager.js +++ b/src/preferences/PreferencesManager.js @@ -489,7 +489,8 @@ define(function (require, exports, module) { * @param {Object} value New value for the preference * @param {{location: ?Object, context: ?Object|string}=} options Specific location in which to set the value or the context to use when setting the value * @param {boolean=} doNotSave True if the preference change should not be saved automatically. - * @return {boolean} true if a value was set + * @return {valid: {boolean}, true if no validator specified or if value is valid + * stored: {boolean}} true if a value was stored */ function set(id, value, options, doNotSave) { if (options && options.context) { @@ -536,7 +537,7 @@ define(function (require, exports, module) { */ function setValueAndSave(id, value, options) { DeprecationWarning.deprecationWarning("setValueAndSave called for " + id + ". Use set instead."); - var changed = set(id, value, options); + var changed = set(id, value, options).stored; PreferencesImpl.manager.save(); return changed; } diff --git a/src/utils/ValidationUtils.js b/src/utils/ValidationUtils.js new file mode 100644 index 00000000000..87f9c41ccbd --- /dev/null +++ b/src/utils/ValidationUtils.js @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + + +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ +/*global define, $, window */ + +define(function (require, exports, module) { + "use strict"; + + + /** + * Used to validate whether type of unknown value is an integer. + * + * @param {*} value Value for which to validate its type + * @return {boolean} true if value is an integer + */ + function isInteger(value) { + // Validate value is a number + if (typeof (value) !== "number" || isNaN(parseInt(value, 10))) { + return false; + } + + // Validate number is an integer + if (value > 0 && Math.floor(value) !== value) { + return false; + } else if (value < 0 && Math.ceil(value) !== value) { + return false; + } + + return true; + } + + /** + * Used to validate whether type of unknown value is an integer, and, if so, + * is it within the option lower and upper limits. + * + * @param {*} value Value for which to validate its type + * @param {number=} lowerLimit Optional lower limit (inclusive) + * @param {number=} upperLimit Optional upper limit (inclusive) + * @return {boolean} true if value is an interger, and optionally in specified range. + */ + function isIntegerInRange(value, lowerLimit, upperLimit) { + // Validate value is an integer + if (!isInteger(value)) { + return false; + } + + // Validate integer is in range + var hasLowerLimt = (typeof (lowerLimit) === "number"), + hasUpperLimt = (typeof (upperLimit) === "number"); + + return ((!hasLowerLimt || value >= lowerLimit) && (!hasUpperLimt || value <= upperLimit)); + } + + + // Define public API + exports.isInteger = isInteger; + exports.isIntegerInRange = isIntegerInRange; +}); diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index d7c7c943045..92e05550c02 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -72,6 +72,7 @@ define(function (require, exports, module) { require("spec/TextRange-test"); require("spec/UpdateNotification-test"); require("spec/UrlParams-test"); + require("spec/ValidationUtils-test"); require("spec/ViewCommandHandlers-test"); require("spec/ViewUtils-test"); require("spec/WorkingSetView-test"); diff --git a/test/spec/Editor-test.js b/test/spec/Editor-test.js index cc82f4c2517..8ec1cf2aa05 100644 --- a/test/spec/Editor-test.js +++ b/test/spec/Editor-test.js @@ -295,6 +295,8 @@ define(function (require, exports, module) { expect(myEditor.getColOffset({line: 5, ch: 4})).toBe(9); // Tab size 2 + var defaultTabSize = Editor.getTabSize(); + expect(defaultTabSize).toBe(4); Editor.setTabSize(2); expect(myEditor.getColOffset({line: 1, ch: 0})).toBe(0); // first line is all spaces: should be unchanged @@ -313,7 +315,7 @@ define(function (require, exports, module) { expect(myEditor.getColOffset({line: 5, ch: 4})).toBe(5); // Restore default - Editor.setTabSize(4); + Editor.setTabSize(defaultTabSize); }); }); diff --git a/test/spec/PreferencesBase-test-files/.brackets.json b/test/spec/PreferencesBase-test-files/.brackets.json index 63279938bb6..7af2e801747 100644 --- a/test/spec/PreferencesBase-test-files/.brackets.json +++ b/test/spec/PreferencesBase-test-files/.brackets.json @@ -1,8 +1,8 @@ { - "spaceUnits": 92, + "spaceUnits": 9, "path": { "foo.go": { - "spaceUnits": 27 + "spaceUnits": 7 } } } \ No newline at end of file diff --git a/test/spec/PreferencesBase-test.js b/test/spec/PreferencesBase-test.js index 69fca88cfa6..a90585371c3 100644 --- a/test/spec/PreferencesBase-test.js +++ b/test/spec/PreferencesBase-test.js @@ -278,7 +278,7 @@ define(function (require, exports, module) { // MemoryStorage operates synchronously scope.load(); - expect(pm.set("foo", foo)).toBe(true); + expect(pm.set("foo", foo).stored).toBe(true); expect(pm.get("foo").value).toBe(42); expect(scope._dirty).toBe(true); @@ -289,7 +289,7 @@ define(function (require, exports, module) { foo.value = "!!!"; expect(foo.value).toBe("!!!"); - expect(pm.set("foo", foo)).toBe(true); + expect(pm.set("foo", foo).stored).toBe(true); expect(scope._dirty).toBe(true); var fooCopyFromPref = pm.get("foo"); @@ -565,7 +565,7 @@ define(function (require, exports, module) { location: { scope: "nonscope" } - })).toBe(false); + }).stored).toBe(false); }); it("supports nested scopes", function () { @@ -894,20 +894,20 @@ define(function (require, exports, module) { location: { scope: "doesNotExist" } - })).toBe(false); + }).stored).toBe(false); expect(pm.get("spaceUnits")).toBe(4); expect(changes).toBe(0); expect(pm.getPreferenceLocation("spaceUnits")).toEqual({ scope: "default" }); - expect(pm.set("spaceUnits", 6)).toBe(true); + expect(pm.set("spaceUnits", 6).stored).toBe(true); expect(user.data).toEqual({ spaceUnits: 6 }); expect(changes).toBe(1); - expect(pm.set("spaceUnits", 7)).toBe(true); + expect(pm.set("spaceUnits", 7).stored).toBe(true); expect(user.data).toEqual({ spaceUnits: 7 }); @@ -917,7 +917,7 @@ define(function (require, exports, module) { location: { scope: "session" } - })).toBe(true); + }).stored).toBe(true); expect(user.data).toEqual({ spaceUnits: 7 }); @@ -926,7 +926,7 @@ define(function (require, exports, module) { }); expect(changes).toBe(3); - expect(pm.set("spaceUnits", 9)).toBe(true); + expect(pm.set("spaceUnits", 9).stored).toBe(true); expect(changes).toBe(4); expect(session.data).toEqual({ spaceUnits: 9 @@ -936,7 +936,7 @@ define(function (require, exports, module) { location: { scope: "session" } - })).toBe(true); + }).stored).toBe(true); expect(changes).toBe(5); expect(session.data.spaceUnits).toBeUndefined(); expect(pm.get("spaceUnits")).toBe(7); @@ -945,7 +945,7 @@ define(function (require, exports, module) { pm.setDefaultFilename("/index.html"); expect(changes).toBe(6); expect(pm.get("spaceUnits")).toBe(2); - expect(pm.set("spaceUnits", 10)).toBe(true); + expect(pm.set("spaceUnits", 10).stored).toBe(true); expect(changes).toBe(7); expect(project.data.path["**.html"].spaceUnits).toBe(10); @@ -957,11 +957,11 @@ define(function (require, exports, module) { location: { scope: "project" } - })).toBe(true); + }).stored).toBe(true); expect(pm.getPreferenceLocation("spaceUnits")).toEqual({ scope: "project" }); - expect(pm.set("spaceUnits", 12)).toBe(true); + expect(pm.set("spaceUnits", 12).stored).toBe(true); expect(project.data.spaceUnits).toBe(12); expect(pm.set("spaceUnits", 13, { @@ -970,7 +970,7 @@ define(function (require, exports, module) { layer: "path", layerID: "**.js" } - })).toBe(true); + }).stored).toBe(true); expect(pm.getPreferenceLocation("spaceUnits")).toEqual({ scope: "project" }); @@ -1064,6 +1064,22 @@ define(function (require, exports, module) { expect(saveDone).toBe(true); }); + + it("should support validator to ignore invalid values", function () { + var pm = new PreferencesBase.PreferencesSystem(); + pm.addScope("user", new PreferencesBase.MemoryStorage()); + pm.definePreference("spaceUnits", "number", 4, { + validator: function (value) { + return (value >= 0 && value <= 10); + } + }); + + expect(pm.set("spaceUnits", 12).valid).toBe(false); // fail: out-of-range upper + expect(pm.get("spaceUnits")).toBe(4); // expect default + + expect(pm.set("spaceUnits", -1).valid).toBe(false); // fail: out-of-range lower + expect(pm.get("spaceUnits")).toBe(4); // expect default + }); }); describe("File Storage", function () { @@ -1119,12 +1135,29 @@ define(function (require, exports, module) { waitsForDone(pm.addScope("project", projectScope)); runs(function () { projectScope.addLayer(new PreferencesBase.PathLayer("/")); - expect(pm.get("spaceUnits")).toBe(92); + expect(pm.get("spaceUnits")).toBe(9); expect(pm.get("spaceUnits", { scopeOrder: ["project"], filename: "/foo.go" - })).toBe(27); + })).toBe(7); + }); + }); + + it("can validate preferences loaded from disk", function () { + var filestorage = new PreferencesBase.FileStorage(settingsFile.fullPath); + var pm = new PreferencesBase.PreferencesSystem(); + var projectScope = new PreferencesBase.Scope(filestorage); + waitsForDone(pm.addScope("project", projectScope)); + runs(function () { + projectScope.addLayer(new PreferencesBase.PathLayer("/")); + pm.definePreference("spaceUnits", "number", 3, { + validator: function (value) { + return (value >= 0 && value <= 8); + } + }); + // Value on disk (9) is out-of-range, so expect default (3) + expect(pm.get("spaceUnits")).toBe(3); }); }); @@ -1202,7 +1235,7 @@ define(function (require, exports, module) { return changes.length > 0; }); runs(function () { - expect(pm.get("spaceUnits")).toBe(92); + expect(pm.get("spaceUnits")).toBe(9); expect(changes).toEqual([{ ids: ["spaceUnits"] }]); diff --git a/test/spec/PreferencesManager-test.js b/test/spec/PreferencesManager-test.js index 804c53ce6d4..67d0dba345d 100644 --- a/test/spec/PreferencesManager-test.js +++ b/test/spec/PreferencesManager-test.js @@ -134,14 +134,14 @@ define(function (require, exports, module) { waitsForDone(SpecRunnerUtils.openProjectFiles(".brackets.json")); runs(function () { - expect(PreferencesManager.get("spaceUnits")).toBe(92); + expect(PreferencesManager.get("spaceUnits")).toBe(9); waitsForDone(FileViewController.openAndSelectDocument(nonProjectFile, FileViewController.WORKING_SET_VIEW)); }); runs(function () { - expect(PreferencesManager.get("spaceUnits")).not.toBe(92); + expect(PreferencesManager.get("spaceUnits")).not.toBe(9); // Changing projects will force a change in the project scope. SpecRunnerUtils.loadProjectInTestWindow(projectWithoutSettings); @@ -150,7 +150,7 @@ define(function (require, exports, module) { waitsForDone(SpecRunnerUtils.openProjectFiles("file_one.js")); }); runs(function () { - expect(PreferencesManager.get("spaceUnits")).not.toBe(92); + expect(PreferencesManager.get("spaceUnits")).not.toBe(9); }); }); diff --git a/test/spec/ValidationUtils-test.js b/test/spec/ValidationUtils-test.js new file mode 100644 index 00000000000..44630da5ffa --- /dev/null +++ b/test/spec/ValidationUtils-test.js @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + + +/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ +/*global define, describe, it, expect, beforeEach, afterEach, runs */ + +define(function (require, exports, module) { + 'use strict'; + + var ValidationUtils = require("utils/ValidationUtils"); + + describe("ValidationUtils", function () { + it("should accept valid integers as integer", function () { + expect(ValidationUtils.isInteger(0)).toBe(true); + expect(ValidationUtils.isInteger(12)).toBe(true); + expect(ValidationUtils.isInteger(+2)).toBe(true); + expect(ValidationUtils.isInteger(-5)).toBe(true); + expect(ValidationUtils.isInteger(3e07)).toBe(true); + expect(ValidationUtils.isInteger(5.28e04)).toBe(true); + }); + + it("should not accept non-numbers as integer", function () { + expect(ValidationUtils.isInteger(NaN)).toBe(false); + expect(ValidationUtils.isInteger(null)).toBe(false); + expect(ValidationUtils.isInteger(undefined)).toBe(false); + expect(ValidationUtils.isInteger("4")).toBe(false); + expect(ValidationUtils.isInteger(false)).toBe(false); + expect(ValidationUtils.isInteger([0, 1, 2])).toBe(false); + expect(ValidationUtils.isInteger({ch: 0, line: 0})).toBe(false); + }); + + it("should not accept non-integer numbers as integer", function () { + expect(ValidationUtils.isInteger(-Infinity)).toBe(false); + expect(ValidationUtils.isInteger(Math.PI)).toBe(false); + expect(ValidationUtils.isInteger(0.375)).toBe(false); + expect(ValidationUtils.isInteger(5e-1)).toBe(false); + expect(ValidationUtils.isInteger(3.29834e-02)).toBe(false); + }); + + // ValidationUtils.isIntegerInRange() uses ValidationUtils.isInteger() to + // validate value which is tested above, so no need to test non-integers + it("should accept integers in range", function () { + // Range limits are optional + expect(ValidationUtils.isIntegerInRange(1)).toBe(true); + expect(ValidationUtils.isIntegerInRange(3, 0)).toBe(true); + expect(ValidationUtils.isIntegerInRange(12, null, 100)).toBe(true); + expect(ValidationUtils.isIntegerInRange(-2, -10, +10)).toBe(true); + expect(ValidationUtils.isIntegerInRange(13, -Infinity, Infinity)).toBe(true); + }); + + // Range limits are optional, so they can be specified as null or undefined to + // indicate that end of range should not be enforced, so verify a value of 0 + // (which evaluates to falsey) can be enforced. + it("should accept optional range limit of zero", function () { + expect(ValidationUtils.isIntegerInRange(2, 0, 10)).toBe(true); + expect(ValidationUtils.isIntegerInRange(-2, 0, 10)).toBe(false); + expect(ValidationUtils.isIntegerInRange(-62, -100, 0)).toBe(true); + expect(ValidationUtils.isIntegerInRange(62, -100, 0)).toBe(false); + }); + + it("should not accept integers out of range", function () { + expect(ValidationUtils.isIntegerInRange(21, null, 20)).toBe(false); + expect(ValidationUtils.isIntegerInRange(4, 5)).toBe(false); + expect(ValidationUtils.isIntegerInRange(12, 1, 10)).toBe(false); + expect(ValidationUtils.isIntegerInRange(-1000, -100, 100)).toBe(false); + }); + }); +});