mirror of
https://github.com/adobe/brackets.git
synced 2024-11-20 09:53:00 +01:00
Merge pull request #11184 from adobe/kai/disable-enable-extensions
Disable and enable extensions
This commit is contained in:
commit
25011360b1
4
.gitignore
vendored
4
.gitignore
vendored
@ -20,6 +20,9 @@ Thumbs.db
|
||||
|
||||
/src/extensions/disabled
|
||||
|
||||
# ignore .disabled file for default extensions
|
||||
/src/extensions/default/*/.disabled
|
||||
|
||||
#OSX .DS_Store files
|
||||
.DS_Store
|
||||
|
||||
@ -34,3 +37,4 @@ Thumbs.db
|
||||
|
||||
# Files that can be automatically downloaded that we don't want to ship with our builds
|
||||
/src/extensibility/node/node_modules/request/tests/
|
||||
|
||||
|
@ -67,6 +67,7 @@ define(function (require, exports, module) {
|
||||
* Extension status constants.
|
||||
*/
|
||||
var ENABLED = "enabled",
|
||||
DISABLED = "disabled",
|
||||
START_FAILED = "startFailed";
|
||||
|
||||
/**
|
||||
@ -103,8 +104,9 @@ define(function (require, exports, module) {
|
||||
/**
|
||||
* Requested changes to the installed extensions.
|
||||
*/
|
||||
var _idsToRemove = [],
|
||||
_idsToUpdate = [];
|
||||
var _idsToRemove = {},
|
||||
_idsToUpdate = {},
|
||||
_idsToDisable = {};
|
||||
|
||||
PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined);
|
||||
|
||||
@ -186,8 +188,9 @@ define(function (require, exports, module) {
|
||||
*/
|
||||
function _reset() {
|
||||
exports.extensions = extensions = {};
|
||||
_idsToRemove = [];
|
||||
_idsToUpdate = [];
|
||||
_idsToRemove = {};
|
||||
_idsToUpdate = {};
|
||||
_idsToDisable = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,8 +243,9 @@ define(function (require, exports, module) {
|
||||
* @param {string} path The local path of the loaded extension's folder.
|
||||
*/
|
||||
function _handleExtensionLoad(e, path) {
|
||||
function setData(id, metadata) {
|
||||
function setData(metadata) {
|
||||
var locationType,
|
||||
id = metadata.name,
|
||||
userExtensionPath = ExtensionLoader.getUserExtensionPath();
|
||||
if (path.indexOf(userExtensionPath) === 0) {
|
||||
locationType = LOCATION_USER;
|
||||
@ -265,27 +269,33 @@ define(function (require, exports, module) {
|
||||
metadata: metadata,
|
||||
path: path,
|
||||
locationType: locationType,
|
||||
status: (e.type === "loadFailed" ? START_FAILED : ENABLED)
|
||||
status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED))
|
||||
};
|
||||
|
||||
synchronizeEntry(id);
|
||||
loadTheme(id);
|
||||
exports.trigger("statusChange", id);
|
||||
}
|
||||
|
||||
function deduceMetadata() {
|
||||
var match = path.match(/\/([^\/]+)$/),
|
||||
name = (match && match[1]) || path,
|
||||
metadata = { name: name, title: name };
|
||||
return metadata;
|
||||
}
|
||||
|
||||
ExtensionUtils.loadPackageJson(path)
|
||||
ExtensionUtils.loadMetadata(path)
|
||||
.done(function (metadata) {
|
||||
setData(metadata.name, metadata);
|
||||
setData(metadata);
|
||||
})
|
||||
.fail(function () {
|
||||
.fail(function (disabled) {
|
||||
// If there's no package.json, this is a legacy extension. It was successfully loaded,
|
||||
// but we don't have an official ID or metadata for it, so we just create an id and
|
||||
// "title" for it (which is the last segment of its pathname)
|
||||
// and record that it's enabled.
|
||||
var match = path.match(/\/([^\/]+)$/),
|
||||
name = (match && match[1]) || path,
|
||||
metadata = { name: name, title: name };
|
||||
setData(name, metadata);
|
||||
var metadata = deduceMetadata();
|
||||
metadata.disabled = disabled;
|
||||
setData(metadata);
|
||||
});
|
||||
}
|
||||
|
||||
@ -398,6 +408,58 @@ define(function (require, exports, module) {
|
||||
}
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Disables or enables the installed extensions.
|
||||
*
|
||||
* @param {string} id The id of the extension to disable or enable.
|
||||
* @param {boolean} enable A boolean indicating whether to enable or disable.
|
||||
* @return {$.Promise} A promise that's resolved when the extension action is
|
||||
* completed or rejected with an error that prevents the action from completion.
|
||||
*/
|
||||
function _enableOrDisable(id, enable) {
|
||||
var result = new $.Deferred(),
|
||||
extension = extensions[id];
|
||||
if (extension && extension.installInfo) {
|
||||
Package[(enable ? "enable" : "disable")](extension.installInfo.path)
|
||||
.done(function () {
|
||||
extension.installInfo.status = enable ? ENABLED : DISABLED;
|
||||
extension.installInfo.metadata.disabled = !enable;
|
||||
result.resolve();
|
||||
exports.trigger("statusChange", id);
|
||||
})
|
||||
.fail(function (err) {
|
||||
result.reject(err);
|
||||
});
|
||||
} else {
|
||||
result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id));
|
||||
}
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the installed extension with the given id.
|
||||
*
|
||||
* @param {string} id The id of the extension to disable.
|
||||
* @return {$.Promise} A promise that's resolved when the extenion is disabled or
|
||||
* rejected with an error that prevented the disabling.
|
||||
*/
|
||||
function disable(id) {
|
||||
return _enableOrDisable(id, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the installed extension with the given id.
|
||||
*
|
||||
* @param {string} id The id of the extension to enable.
|
||||
* @return {$.Promise} A promise that's resolved when the extenion is enabled or
|
||||
* rejected with an error that prevented the enabling.
|
||||
*/
|
||||
function enable(id) {
|
||||
return _enableOrDisable(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an installed extension with the given package file.
|
||||
@ -452,7 +514,7 @@ define(function (require, exports, module) {
|
||||
}
|
||||
exports.trigger("statusChange", id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if an extension is marked for removal.
|
||||
* @param {string} id The id of the extension to check.
|
||||
@ -461,7 +523,7 @@ define(function (require, exports, module) {
|
||||
function isMarkedForRemoval(id) {
|
||||
return !!(_idsToRemove[id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if there are any extensions marked for removal.
|
||||
* @return {boolean} true if there are extensions to remove
|
||||
@ -470,6 +532,46 @@ define(function (require, exports, module) {
|
||||
return Object.keys(_idsToRemove).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an extension for disabling later, or unmarks an extension previously marked.
|
||||
*
|
||||
* @param {string} id The id of the extension
|
||||
* @param {boolean} mark Whether to mark or unmark the extension.
|
||||
*/
|
||||
function markForDisabling(id, mark) {
|
||||
if (mark) {
|
||||
_idsToDisable[id] = true;
|
||||
} else {
|
||||
delete _idsToDisable[id];
|
||||
}
|
||||
exports.trigger("statusChange", id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an extension is mark for disabling.
|
||||
*
|
||||
* @param {string} id The id of the extension to check.
|
||||
* @return {boolean} true if it's been mark for disabling, false otherwise.
|
||||
*/
|
||||
function isMarkedForDisabling(id) {
|
||||
return !!(_idsToDisable[id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are any extensions marked for disabling.
|
||||
* @return {boolean} true if there are extensions to disable
|
||||
*/
|
||||
function hasExtensionsToDisable() {
|
||||
return Object.keys(_idsToDisable).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarks all the extensions that have been marked for disabling.
|
||||
*/
|
||||
function unmarkAllForDisabling() {
|
||||
_idsToDisable = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* If a downloaded package appears to be an update, mark the extension for update.
|
||||
* If an extension was previously marked for removal, marking for update will
|
||||
@ -542,6 +644,25 @@ define(function (require, exports, module) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables extensions marked for disabling.
|
||||
*
|
||||
* If the return promise is rejected, the argument will contain an array of objects. Each
|
||||
* element is an object identifying the extension failed with "item" property set to the
|
||||
* extension id which has failed to be disabled and "error" property set to the error.
|
||||
*
|
||||
* @return {$.Promise} A promise that's resolved when all extensions marked for disabling are
|
||||
* disabled or rejected if one or more extensions can't be disabled.
|
||||
*/
|
||||
function disableMarkedExtensions() {
|
||||
return Async.doInParallel_aggregateErrors(
|
||||
Object.keys(_idsToDisable),
|
||||
function (id) {
|
||||
return disable(id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates extensions previously marked for update.
|
||||
@ -764,7 +885,8 @@ define(function (require, exports, module) {
|
||||
// Listen to extension load and loadFailed events
|
||||
ExtensionLoader
|
||||
.on("load", _handleExtensionLoad)
|
||||
.on("loadFailed", _handleExtensionLoad);
|
||||
.on("loadFailed", _handleExtensionLoad)
|
||||
.on("disabled", _handleExtensionLoad);
|
||||
|
||||
|
||||
EventDispatcher.makeEventDispatcher(exports);
|
||||
@ -775,17 +897,24 @@ define(function (require, exports, module) {
|
||||
exports.getExtensionURL = getExtensionURL;
|
||||
exports.remove = remove;
|
||||
exports.update = update;
|
||||
exports.disable = disable;
|
||||
exports.enable = enable;
|
||||
exports.extensions = extensions;
|
||||
exports.cleanupUpdates = cleanupUpdates;
|
||||
exports.markForRemoval = markForRemoval;
|
||||
exports.isMarkedForRemoval = isMarkedForRemoval;
|
||||
exports.unmarkAllForRemoval = unmarkAllForRemoval;
|
||||
exports.hasExtensionsToRemove = hasExtensionsToRemove;
|
||||
exports.markForDisabling = markForDisabling;
|
||||
exports.isMarkedForDisabling = isMarkedForDisabling;
|
||||
exports.unmarkAllForDisabling = unmarkAllForDisabling;
|
||||
exports.hasExtensionsToDisable = hasExtensionsToDisable;
|
||||
exports.updateFromDownload = updateFromDownload;
|
||||
exports.removeUpdate = removeUpdate;
|
||||
exports.isMarkedForUpdate = isMarkedForUpdate;
|
||||
exports.hasExtensionsToUpdate = hasExtensionsToUpdate;
|
||||
exports.removeMarkedExtensions = removeMarkedExtensions;
|
||||
exports.disableMarkedExtensions = disableMarkedExtensions;
|
||||
exports.updateExtensions = updateExtensions;
|
||||
exports.getAvailableUpdates = getAvailableUpdates;
|
||||
exports.cleanAvailableUpdates = cleanAvailableUpdates;
|
||||
@ -793,6 +922,7 @@ define(function (require, exports, module) {
|
||||
exports.hasDownloadedRegistry = false;
|
||||
|
||||
exports.ENABLED = ENABLED;
|
||||
exports.DISABLED = DISABLED;
|
||||
exports.START_FAILED = START_FAILED;
|
||||
|
||||
exports.LOCATION_DEFAULT = LOCATION_DEFAULT;
|
||||
|
@ -63,17 +63,20 @@ define(function (require, exports, module) {
|
||||
*/
|
||||
function _performChanges() {
|
||||
// If an extension was removed or updated, prompt the user to quit Brackets.
|
||||
var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(),
|
||||
hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate();
|
||||
if (!hasRemovedExtensions && !hasUpdatedExtensions) {
|
||||
var hasRemovedExtensions = ExtensionManager.hasExtensionsToRemove(),
|
||||
hasUpdatedExtensions = ExtensionManager.hasExtensionsToUpdate(),
|
||||
hasDisabledExtensions = ExtensionManager.hasExtensionsToDisable();
|
||||
if (!hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) {
|
||||
return;
|
||||
}
|
||||
|
||||
var buttonLabel = Strings.CHANGE_AND_RELOAD;
|
||||
if (hasRemovedExtensions && !hasUpdatedExtensions) {
|
||||
if (hasRemovedExtensions && !hasUpdatedExtensions && !hasDisabledExtensions) {
|
||||
buttonLabel = Strings.REMOVE_AND_RELOAD;
|
||||
} else if (hasUpdatedExtensions && !hasRemovedExtensions) {
|
||||
} else if (hasUpdatedExtensions && !hasRemovedExtensions && !hasDisabledExtensions) {
|
||||
buttonLabel = Strings.UPDATE_AND_RELOAD;
|
||||
} else if (hasDisabledExtensions && !hasRemovedExtensions && !hasUpdatedExtensions) {
|
||||
buttonLabel = Strings.DISABLE_AND_RELOAD;
|
||||
}
|
||||
|
||||
var dlg = Dialogs.showModalDialog(
|
||||
@ -107,62 +110,101 @@ define(function (require, exports, module) {
|
||||
.text(Strings.PROCESSING_EXTENSIONS)
|
||||
.append("<span class='spinner inline spin'/>");
|
||||
|
||||
ExtensionManager.removeMarkedExtensions()
|
||||
.done(function () {
|
||||
ExtensionManager.updateExtensions()
|
||||
.done(function () {
|
||||
dlg.close();
|
||||
CommandManager.execute(Commands.APP_RELOAD);
|
||||
})
|
||||
.fail(function (errorArray) {
|
||||
dlg.close();
|
||||
|
||||
// This error case should be very uncommon.
|
||||
// Just let the user know that we couldn't update
|
||||
// this extension and log the errors to the console.
|
||||
var ids = [];
|
||||
errorArray.forEach(function (errorObj) {
|
||||
ids.push(errorObj.item);
|
||||
if (errorObj.error && errorObj.error.forEach) {
|
||||
console.error("Errors for", errorObj.item);
|
||||
errorObj.error.forEach(function (error) {
|
||||
console.error(Package.formatError(error));
|
||||
});
|
||||
} else {
|
||||
console.error("Error for", errorObj.item, errorObj);
|
||||
}
|
||||
});
|
||||
Dialogs.showModalDialog(
|
||||
DefaultDialogs.DIALOG_ID_ERROR,
|
||||
Strings.EXTENSION_MANAGER_UPDATE,
|
||||
StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", "))
|
||||
).done(function () {
|
||||
// We still have to reload even if some of the removals failed.
|
||||
CommandManager.execute(Commands.APP_RELOAD);
|
||||
});
|
||||
});
|
||||
})
|
||||
var removeExtensionsPromise,
|
||||
updateExtensionsPromise,
|
||||
disableExtensionsPromise,
|
||||
removeErrors,
|
||||
updateErrors,
|
||||
disableErrors;
|
||||
|
||||
removeExtensionsPromise = ExtensionManager.removeMarkedExtensions()
|
||||
.fail(function (errorArray) {
|
||||
removeErrors = errorArray;
|
||||
});
|
||||
updateExtensionsPromise = ExtensionManager.updateExtensions()
|
||||
.fail(function (errorArray) {
|
||||
updateErrors = errorArray;
|
||||
});
|
||||
disableExtensionsPromise = ExtensionManager.disableMarkedExtensions()
|
||||
.fail(function (errorArray) {
|
||||
disableErrors = errorArray;
|
||||
});
|
||||
|
||||
Async.waitForAll([removeExtensionsPromise, updateExtensionsPromise, disableExtensionsPromise], true)
|
||||
.always(function () {
|
||||
dlg.close();
|
||||
ExtensionManager.cleanupUpdates();
|
||||
})
|
||||
.done(function () {
|
||||
CommandManager.execute(Commands.APP_RELOAD);
|
||||
})
|
||||
.fail(function () {
|
||||
var ids = [],
|
||||
dialogs = [];
|
||||
|
||||
function nextDialog() {
|
||||
var dialog = dialogs.shift();
|
||||
if (dialog) {
|
||||
Dialogs.showModalDialog(dialog.dialog, dialog.title, dialog.message)
|
||||
.done(nextDialog);
|
||||
} else {
|
||||
// Even in case of error condition, we still have to reload
|
||||
CommandManager.execute(Commands.APP_RELOAD);
|
||||
}
|
||||
}
|
||||
|
||||
var ids = [];
|
||||
errorArray.forEach(function (errorObj) {
|
||||
ids.push(errorObj.item);
|
||||
});
|
||||
Dialogs.showModalDialog(
|
||||
DefaultDialogs.DIALOG_ID_ERROR,
|
||||
Strings.EXTENSION_MANAGER_REMOVE,
|
||||
StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", "))
|
||||
).done(function () {
|
||||
// We still have to reload even if some of the removals failed.
|
||||
CommandManager.execute(Commands.APP_RELOAD);
|
||||
});
|
||||
if (removeErrors) {
|
||||
removeErrors.forEach(function (errorObj) {
|
||||
ids.push(errorObj.item);
|
||||
});
|
||||
dialogs.push({
|
||||
dialog: DefaultDialogs.DIALOG_ID_ERROR,
|
||||
title: Strings.EXTENSION_MANAGER_REMOVE,
|
||||
message: StringUtils.format(Strings.EXTENSION_MANAGER_REMOVE_ERROR, ids.join(", "))
|
||||
});
|
||||
}
|
||||
|
||||
if (updateErrors) {
|
||||
// This error case should be very uncommon.
|
||||
// Just let the user know that we couldn't update
|
||||
// this extension and log the errors to the console.
|
||||
ids.length = 0;
|
||||
updateErrors.forEach(function (errorObj) {
|
||||
ids.push(errorObj.item);
|
||||
if (errorObj.error && errorObj.error.forEach) {
|
||||
console.error("Errors for", errorObj.item);
|
||||
errorObj.error.forEach(function (error) {
|
||||
console.error(Package.formatError(error));
|
||||
});
|
||||
} else {
|
||||
console.error("Error for", errorObj.item, errorObj);
|
||||
}
|
||||
});
|
||||
dialogs.push({
|
||||
dialog: DefaultDialogs.DIALOG_ID_ERROR,
|
||||
title: Strings.EXTENSION_MANAGER_UPDATE,
|
||||
message: StringUtils.format(Strings.EXTENSION_MANAGER_UPDATE_ERROR, ids.join(", "))
|
||||
});
|
||||
}
|
||||
|
||||
if (disableErrors) {
|
||||
ids.length = 0;
|
||||
disableErrors.forEach(function (errorObj) {
|
||||
ids.push(errorObj.item);
|
||||
});
|
||||
dialogs.push({
|
||||
dialog: DefaultDialogs.DIALOG_ID_ERROR,
|
||||
title: Strings.EXTENSION_MANAGER_DISABLE,
|
||||
message: StringUtils.format(Strings.EXTENSION_MANAGER_DISABLE_ERROR, ids.join(", "))
|
||||
});
|
||||
}
|
||||
|
||||
nextDialog();
|
||||
});
|
||||
} else {
|
||||
dlg.close();
|
||||
ExtensionManager.cleanupUpdates();
|
||||
ExtensionManager.unmarkAllForRemoval();
|
||||
ExtensionManager.unmarkAllForDisabling();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -181,6 +181,8 @@ define(function (require, exports, module) {
|
||||
ExtensionManager.markForRemoval($target.attr("data-extension-id"), true);
|
||||
} else if ($target.hasClass("undo-update")) {
|
||||
ExtensionManager.removeUpdate($target.attr("data-extension-id"));
|
||||
} else if ($target.hasClass("undo-disable")) {
|
||||
ExtensionManager.markForDisabling($target.attr("data-extension-id"), false);
|
||||
} else if ($target.data("toggle-desc") === "expand-desc") {
|
||||
this._toggleDescription($target.attr("data-extension-id"), $target, true);
|
||||
} else if ($target.data("toggle-desc") === "trunc-desc") {
|
||||
@ -195,6 +197,12 @@ define(function (require, exports, module) {
|
||||
})
|
||||
.on("click", "button.remove", function (e) {
|
||||
ExtensionManager.markForRemoval($(e.target).attr("data-extension-id"), true);
|
||||
})
|
||||
.on("click", "button.disable", function (e) {
|
||||
ExtensionManager.markForDisabling($(e.target).attr("data-extension-id"), true);
|
||||
})
|
||||
.on("click", "button.enable", function (e) {
|
||||
ExtensionManager.enable($(e.target).attr("data-extension-id"));
|
||||
});
|
||||
};
|
||||
|
||||
@ -221,6 +229,7 @@ define(function (require, exports, module) {
|
||||
// arrays as iteration contexts.
|
||||
context.isInstalled = !!entry.installInfo;
|
||||
context.failedToStart = (entry.installInfo && entry.installInfo.status === ExtensionManager.START_FAILED);
|
||||
context.disabled = (entry.installInfo && entry.installInfo.status === ExtensionManager.DISABLED);
|
||||
context.hasVersionInfo = !!info.versions;
|
||||
|
||||
if (entry.registryInfo) {
|
||||
@ -259,7 +268,9 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
context.isMarkedForRemoval = ExtensionManager.isMarkedForRemoval(info.metadata.name);
|
||||
context.isMarkedForDisabling = ExtensionManager.isMarkedForDisabling(info.metadata.name);
|
||||
context.isMarkedForUpdate = ExtensionManager.isMarkedForUpdate(info.metadata.name);
|
||||
var hasPendingAction = context.isMarkedForDisabling || context.isMarkedForRemoval || context.isMarkedForUpdate;
|
||||
|
||||
context.showInstallButton = (this.model.source === this.model.SOURCE_REGISTRY || this.model.source === this.model.SOURCE_THEMES) && !context.updateAvailable;
|
||||
context.showUpdateButton = context.updateAvailable && !context.isMarkedForUpdate && !context.isMarkedForRemoval;
|
||||
@ -314,7 +325,11 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
context.removalAllowed = this.model.source === "installed" &&
|
||||
!context.failedToStart && !context.isMarkedForUpdate && !context.isMarkedForRemoval;
|
||||
!context.failedToStart && !hasPendingAction;
|
||||
context.disablingAllowed = this.model.source === "installed" &&
|
||||
!context.disabled && !hasPendingAction;
|
||||
context.enablingAllowed = this.model.source === "installed" &&
|
||||
context.disabled && !hasPendingAction;
|
||||
|
||||
// Copy over helper functions that we share with the registry app.
|
||||
["lastVersionDate", "authorInfo"].forEach(function (helper) {
|
||||
|
@ -432,6 +432,48 @@ define(function (require, exports, module) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the extension at the given path.
|
||||
*
|
||||
* @param {string} path The absolute path to the extension to disable.
|
||||
* @return {$.Promise} A promise that's resolved when the extenion is disabled, or
|
||||
* rejected if there was an error.
|
||||
*/
|
||||
function disable(path) {
|
||||
var result = new $.Deferred(),
|
||||
file = FileSystem.getFileForPath(path + "/.disabled");
|
||||
file.write("", function (err) {
|
||||
if (err) {
|
||||
result.reject(err);
|
||||
} else {
|
||||
result.resolve();
|
||||
}
|
||||
});
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the extension at the given path.
|
||||
*
|
||||
* @param {string} path The absolute path to the extension to enable.
|
||||
* @return {$.Promise} A promise that's resolved when the extenion is enable, or
|
||||
* rejected if there was an error.
|
||||
*/
|
||||
function enable(path) {
|
||||
var result = new $.Deferred(),
|
||||
file = FileSystem.getFileForPath(path + "/.disabled");
|
||||
file.unlink(function (err) {
|
||||
if (err) {
|
||||
result.reject(err);
|
||||
return;
|
||||
}
|
||||
ExtensionLoader.loadExtension(FileUtils.getBaseName(path), { baseUrl: path }, "main")
|
||||
.done(result.resolve)
|
||||
.fail(result.reject);
|
||||
});
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an extension update located at path.
|
||||
* This assumes that the installation was previously attempted
|
||||
@ -500,12 +542,14 @@ define(function (require, exports, module) {
|
||||
// For unit tests only
|
||||
exports._getNodeConnectionDeferred = _getNodeConnectionDeferred;
|
||||
|
||||
exports.installFromURL = installFromURL;
|
||||
exports.installFromPath = installFromPath;
|
||||
exports.validate = validate;
|
||||
exports.install = install;
|
||||
exports.remove = remove;
|
||||
exports.installUpdate = installUpdate;
|
||||
exports.formatError = formatError;
|
||||
exports.InstallationStatuses = InstallationStatuses;
|
||||
exports.installFromURL = installFromURL;
|
||||
exports.installFromPath = installFromPath;
|
||||
exports.validate = validate;
|
||||
exports.install = install;
|
||||
exports.remove = remove;
|
||||
exports.disable = disable;
|
||||
exports.enable = enable;
|
||||
exports.installUpdate = installUpdate;
|
||||
exports.formatError = formatError;
|
||||
exports.InstallationStatuses = InstallationStatuses;
|
||||
});
|
||||
|
@ -51,6 +51,7 @@
|
||||
{{/translated}}
|
||||
</td>
|
||||
<td class="ext-action">
|
||||
<div>
|
||||
{{#showInstallButton}}
|
||||
<button class="btn btn-mini install" data-extension-id="{{metadata.name}}" {{^allowInstall}}disabled{{/allowInstall}}>
|
||||
{{^isInstalled}}{{Strings.INSTALL}}{{/isInstalled}}
|
||||
@ -63,6 +64,19 @@
|
||||
{{Strings.UPDATE}}
|
||||
</button>
|
||||
{{/showUpdateButton}}
|
||||
{{#disablingAllowed}}
|
||||
{{#showUpdateButton}}
|
||||
</div><div>
|
||||
{{/showUpdateButton}}
|
||||
<button class="btn btn-mini disable" data-extension-id="{{metadata.name}}">
|
||||
{{Strings.DISABLE}}
|
||||
</button>
|
||||
{{/disablingAllowed}}
|
||||
{{#enablingAllowed}}
|
||||
<button class="btn btn-mini enable" data-extension-id="{{metadata.name}}">
|
||||
{{Strings.ENABLE}}
|
||||
</button>
|
||||
{{/enablingAllowed}}
|
||||
{{#removalAllowed}}
|
||||
<button class="btn btn-mini remove" data-extension-id="{{metadata.name}}" {{^allowRemove}}disabled title="{{Strings.CANT_REMOVE_DEV}}"{{/allowRemove}}>
|
||||
{{Strings.REMOVE}}
|
||||
@ -76,9 +90,13 @@
|
||||
{{#isMarkedForRemoval}}
|
||||
{{Strings.MARKED_FOR_REMOVAL}} (<a class="undo-remove" data-extension-id="{{metadata.name}}" href="#">{{Strings.UNDO_REMOVE}}</a>)
|
||||
{{/isMarkedForRemoval}}
|
||||
{{#isMarkedForDisabling}}
|
||||
Marked for disabling (<a class="undo-disable" data-extension-id="{{metadata.name}}" href="#">{{Strings.UNDO_DISABLE}}</a>)
|
||||
{{/isMarkedForDisabling}}
|
||||
{{#isMarkedForUpdate}}
|
||||
{{Strings.MARKED_FOR_UPDATE}} (<a class="undo-update" data-extension-id="{{metadata.name}}" href="#">{{Strings.UNDO_UPDATE}}</a>)
|
||||
{{/isMarkedForUpdate}}
|
||||
{{/isInstalled}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -470,6 +470,8 @@ define({
|
||||
"INSTALL" : "Install",
|
||||
"UPDATE" : "Update",
|
||||
"REMOVE" : "Remove",
|
||||
"DISABLE" : "Disable",
|
||||
"ENABLE" : "Enable",
|
||||
"OVERWRITE" : "Overwrite",
|
||||
"CANT_REMOVE_DEV" : "Extensions in the \"dev\" folder must be manually deleted.",
|
||||
"CANT_UPDATE" : "The update isn't compatible with this version of {APP_NAME}.",
|
||||
@ -538,15 +540,20 @@ define({
|
||||
"EXTENSION_MANAGER_REMOVE_ERROR" : "Unable to remove one or more extensions: {0}. {APP_NAME} will still reload.",
|
||||
"EXTENSION_MANAGER_UPDATE" : "Update Extension",
|
||||
"EXTENSION_MANAGER_UPDATE_ERROR" : "Unable to update one or more extensions: {0}. {APP_NAME} will still reload.",
|
||||
"EXTENSION_MANAGER_DISABLE" : "Disable Extension",
|
||||
"EXTENSION_MANAGER_DISABLE_ERROR" : "Unable to disable one or more extensions: {0}. {APP_NAME} will still reload.",
|
||||
"MARKED_FOR_REMOVAL" : "Marked for removal",
|
||||
"UNDO_REMOVE" : "Undo",
|
||||
"MARKED_FOR_UPDATE" : "Marked for update",
|
||||
"UNDO_UPDATE" : "Undo",
|
||||
"MARKED_FOR_DISABLING" : "Marked for disabling",
|
||||
"UNDO_DISABLE" : "Undo",
|
||||
"CHANGE_AND_RELOAD_TITLE" : "Change Extensions",
|
||||
"CHANGE_AND_RELOAD_MESSAGE" : "To update or remove the marked extensions, {APP_NAME} will need to reload. You'll be prompted to save unsaved changes.",
|
||||
"CHANGE_AND_RELOAD_MESSAGE" : "To update, remove or disable the marked extensions, {APP_NAME} will need to reload. You'll be prompted to save unsaved changes.",
|
||||
"REMOVE_AND_RELOAD" : "Remove Extensions and Reload",
|
||||
"CHANGE_AND_RELOAD" : "Change Extensions and Reload",
|
||||
"UPDATE_AND_RELOAD" : "Update Extensions and Reload",
|
||||
"DISABLE_AND_RELOAD" : "Disable Extensions and Reload",
|
||||
"PROCESSING_EXTENSIONS" : "Processing extension changes\u2026",
|
||||
"EXTENSION_NOT_INSTALLED" : "Couldn't remove extension {0} because it wasn't installed.",
|
||||
"NO_EXTENSIONS" : "No extensions installed yet.<br>Click on the Available tab above to get started.",
|
||||
|
@ -1525,6 +1525,7 @@ input[type="color"],
|
||||
box-shadow: inset 0 1px @bc-highlight-hard;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-shadow: none;
|
||||
margin: 3px 0px 3px 3px;
|
||||
|
||||
.dark & {
|
||||
background-color: @dark-bc-btn-bg;
|
||||
@ -1650,7 +1651,6 @@ input[type="color"],
|
||||
color: @bc-text-alt;
|
||||
font-weight: @font-weight-semibold;
|
||||
text-shadow: 0 -1px 0 @bc-shadow-small;
|
||||
margin-right: 3px;
|
||||
|
||||
.dark & {
|
||||
background-color: @dark-bc-secondary-btn-bg;
|
||||
|
@ -242,7 +242,7 @@ define(function (require, exports, module) {
|
||||
var promise = new $.Deferred();
|
||||
|
||||
// Try to load the package.json to figure out if we are loading a theme.
|
||||
ExtensionUtils.loadPackageJson(config.baseUrl).always(promise.resolve);
|
||||
ExtensionUtils.loadMetadata(config.baseUrl).always(promise.resolve);
|
||||
|
||||
return promise
|
||||
.then(function (metadata) {
|
||||
@ -251,12 +251,20 @@ define(function (require, exports, module) {
|
||||
return;
|
||||
}
|
||||
|
||||
return loadExtensionModule(name, config, entryPoint);
|
||||
if (!metadata.disabled) {
|
||||
return loadExtensionModule(name, config, entryPoint);
|
||||
} else {
|
||||
return new $.Deferred().reject("disabled").promise();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
exports.trigger("load", config.baseUrl);
|
||||
}, function (err) {
|
||||
exports.trigger("loadFailed", config.baseUrl);
|
||||
if (err === "disabled") {
|
||||
exports.trigger("disabled", config.baseUrl);
|
||||
} else {
|
||||
exports.trigger("loadFailed", config.baseUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,8 @@
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var FileSystem = require("filesystem/FileSystem"),
|
||||
var Async = require("utils/Async"),
|
||||
FileSystem = require("filesystem/FileSystem"),
|
||||
FileUtils = require("file/FileUtils");
|
||||
|
||||
/**
|
||||
@ -233,26 +234,51 @@ define(function (require, exports, module) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the package.json file in the given extension folder.
|
||||
* Loads the package.json file in the given extension folder as well as any additional
|
||||
* metadata.
|
||||
*
|
||||
* If there's a .disabled file in the extension directory, then the content of package.json
|
||||
* will be augmented with disabled property set to true. It will override whatever value of
|
||||
* disabled might be set.
|
||||
*
|
||||
* @param {string} folder The extension folder.
|
||||
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
|
||||
* or rejected if there is no package.json or the contents are not valid JSON.
|
||||
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
|
||||
*/
|
||||
function loadPackageJson(folder) {
|
||||
var file = FileSystem.getFileForPath(folder + "/package.json"),
|
||||
result = new $.Deferred();
|
||||
FileUtils.readAsText(file)
|
||||
.done(function (text) {
|
||||
function loadMetadata(folder) {
|
||||
var packageJSONFile = FileSystem.getFileForPath(folder + "/package.json"),
|
||||
disabledFile = FileSystem.getFileForPath(folder + "/.disabled"),
|
||||
result = new $.Deferred(),
|
||||
jsonPromise = new $.Deferred(),
|
||||
disabledPromise = new $.Deferred(),
|
||||
json,
|
||||
disabled;
|
||||
FileUtils.readAsText(packageJSONFile)
|
||||
.then(function (text) {
|
||||
try {
|
||||
var json = JSON.parse(text);
|
||||
result.resolve(json);
|
||||
json = JSON.parse(text);
|
||||
jsonPromise.resolve();
|
||||
} catch (e) {
|
||||
result.reject();
|
||||
jsonPromise.reject();
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
result.reject();
|
||||
.fail(jsonPromise.reject);
|
||||
disabledFile.exists(function (err, exists) {
|
||||
if (err) {
|
||||
disabled = false;
|
||||
} else {
|
||||
disabled = exists;
|
||||
}
|
||||
disabledPromise.resolve();
|
||||
});
|
||||
Async.waitForAll([jsonPromise, disabledPromise])
|
||||
.always(function () {
|
||||
if (!json) {
|
||||
result.reject(disabled);
|
||||
} else {
|
||||
json.disabled = disabled;
|
||||
result.resolve(json);
|
||||
}
|
||||
});
|
||||
return result.promise();
|
||||
}
|
||||
@ -264,5 +290,5 @@ define(function (require, exports, module) {
|
||||
exports.getModuleUrl = getModuleUrl;
|
||||
exports.loadFile = loadFile;
|
||||
exports.loadStyleSheet = loadStyleSheet;
|
||||
exports.loadPackageJson = loadPackageJson;
|
||||
exports.loadMetadata = loadMetadata;
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ define(function (require, exports, module) {
|
||||
|
||||
describe("ExtensionManager", function () {
|
||||
var mockId, mockSettings, origRegistryURL, origExtensionUrl, removedPath,
|
||||
view, model, fakeLoadDeferred, modelDisposed;
|
||||
view, model, fakeLoadDeferred, modelDisposed, disabledFilePath;
|
||||
|
||||
beforeEach(function () {
|
||||
// Use fake URLs for the registry (useful if the registry isn't actually currently
|
||||
@ -94,6 +94,17 @@ define(function (require, exports, module) {
|
||||
removedPath = path;
|
||||
return new $.Deferred().resolve().promise();
|
||||
});
|
||||
|
||||
// Fake enabling/disabling
|
||||
disabledFilePath = null;
|
||||
spyOn(Package, "disable").andCallFake(function (path) {
|
||||
disabledFilePath = path + "/.disabled";
|
||||
return new $.Deferred().resolve().promise();
|
||||
});
|
||||
spyOn(Package, "enable").andCallFake(function (path) {
|
||||
disabledFilePath = path + "/.disabled";
|
||||
return new $.Deferred().resolve().promise();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -105,7 +116,14 @@ define(function (require, exports, module) {
|
||||
});
|
||||
|
||||
function mockLoadExtensions(names, fail) {
|
||||
var numStatusChanges = 0;
|
||||
var numStatusChanges = 0,
|
||||
shouldFail = false,
|
||||
shouldDisable = false;
|
||||
if (typeof fail === "boolean") {
|
||||
shouldFail = true;
|
||||
} else if (typeof fail === "string") {
|
||||
shouldDisable = true;
|
||||
}
|
||||
runs(function () {
|
||||
ExtensionManager.on("statusChange.mock-load", function () {
|
||||
numStatusChanges++;
|
||||
@ -113,7 +131,7 @@ define(function (require, exports, module) {
|
||||
var mockPath = SpecRunnerUtils.getTestPath("/spec/ExtensionManager-test-files");
|
||||
names = names || ["default/mock-extension-1", "dev/mock-extension-2", "user/mock-legacy-extension"];
|
||||
names.forEach(function (name) {
|
||||
ExtensionLoader.trigger(fail ? "loadFailed" : "load", mockPath + "/" + name);
|
||||
ExtensionLoader.trigger(shouldFail ? "loadFailed" : (shouldDisable ? "disabled" : "load"), mockPath + "/" + name);
|
||||
});
|
||||
});
|
||||
|
||||
@ -302,6 +320,16 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should list an extension that is installed but disabled", function () {
|
||||
runs(function () {
|
||||
waitsForDone(ExtensionManager.downloadRegistry(), "loading registry");
|
||||
});
|
||||
mockLoadExtensions(["user/mock-extension-3"], "disabled");
|
||||
runs(function () {
|
||||
expect(ExtensionManager.extensions["mock-extension-3"].installInfo.status).toEqual(ExtensionManager.DISABLED);
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the title for a legacy extension based on its folder name", function () {
|
||||
mockLoadExtensions();
|
||||
runs(function () {
|
||||
@ -357,6 +385,40 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable an extension and raise a statusChange event", function () {
|
||||
var spy = jasmine.createSpy();
|
||||
runs(function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
});
|
||||
runs(function () {
|
||||
ExtensionManager.on("statusChange.unit-test", spy);
|
||||
waitsForDone(ExtensionManager.disable("mock-extension-3"));
|
||||
});
|
||||
runs(function () {
|
||||
var mockPath = SpecRunnerUtils.getTestPath("/spec/ExtensionManager-test-files");
|
||||
expect(disabledFilePath).toBe(mockPath + "/user/mock-extension-3" + "/.disabled");
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.any(Object), "mock-extension-3");
|
||||
expect(ExtensionManager.extensions["mock-extension-3"].installInfo.status).toEqual(ExtensionManager.DISABLED);
|
||||
});
|
||||
});
|
||||
|
||||
it("should enable an extension and raise a statusChange event", function () {
|
||||
var spy = jasmine.createSpy();
|
||||
runs(function () {
|
||||
mockLoadExtensions(["user/mock-extension-2"], "disable");
|
||||
});
|
||||
runs(function () {
|
||||
ExtensionManager.on("statusChange.unit-test", spy);
|
||||
waitsForDone(ExtensionManager.enable("mock-extension-2"));
|
||||
});
|
||||
runs(function () {
|
||||
var mockPath = SpecRunnerUtils.getTestPath("/spec/ExtensionManager-test-files");
|
||||
expect(disabledFilePath).toBe(mockPath + "/user/mock-extension-2" + "/.disabled");
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.any(Object), "mock-extension-2");
|
||||
expect(ExtensionManager.extensions["mock-extension-2"].installInfo.status).toEqual(ExtensionManager.ENABLED);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail when trying to remove an extension that's not installed", function () {
|
||||
var finished = false;
|
||||
runs(function () {
|
||||
@ -372,6 +434,36 @@ define(function (require, exports, module) {
|
||||
waitsFor(function () { return finished; }, "finish removal");
|
||||
});
|
||||
|
||||
it("should fail when trying to disable an extension that's not installed", function () {
|
||||
var finished = false;
|
||||
runs(function () {
|
||||
ExtensionManager.disable("mock-extension-3")
|
||||
.done(function () {
|
||||
finished = true;
|
||||
expect("tried to disable a nonexistent extension").toBe(false);
|
||||
})
|
||||
.fail(function () {
|
||||
finished = true;
|
||||
});
|
||||
});
|
||||
waitsFor(function () { return finished; }, "finish disabling");
|
||||
});
|
||||
|
||||
it("should fail when trying to enable an extension that's not installed", function () {
|
||||
var finished = false;
|
||||
runs(function () {
|
||||
ExtensionManager.enable("mock-extension-3")
|
||||
.done(function () {
|
||||
finished = true;
|
||||
expect("tried to enable a nonexistent extension").toBe(false);
|
||||
})
|
||||
.fail(function () {
|
||||
finished = true;
|
||||
});
|
||||
});
|
||||
waitsFor(function () { return finished; }, "finish enabling");
|
||||
});
|
||||
|
||||
it("should calculate compatibility info for installed extensions", function () {
|
||||
function fakeEntry(version) {
|
||||
return { metadata: { engines: { brackets: version } } };
|
||||
@ -775,6 +867,13 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should include a newly-installed disabled extension", function () {
|
||||
mockLoadExtensions(["user/another-great-extension"], "disabled");
|
||||
runs(function () {
|
||||
expect(model.filterSet.indexOf("another-great-extension")).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should raise an event when an extension is installed", function () {
|
||||
var calledId;
|
||||
runs(function () {
|
||||
@ -788,6 +887,19 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should raise an event when an extension is disabled", function () {
|
||||
var calledId;
|
||||
runs(function () {
|
||||
model.on("change", function (e, id) {
|
||||
calledId = id;
|
||||
});
|
||||
});
|
||||
mockLoadExtensions(["user/another-great-extension"], "disabled");
|
||||
runs(function () {
|
||||
expect(calledId).toBe("another-great-extension");
|
||||
});
|
||||
});
|
||||
|
||||
it("should not include a removed extension", function () {
|
||||
runs(function () {
|
||||
waitsForDone(ExtensionManager.remove("registered-extension"));
|
||||
@ -860,6 +972,74 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should mark an extension for disabling and raise an event", function () {
|
||||
var id = "registered-extension", calledId;
|
||||
runs(function () {
|
||||
model.on("change", function (e, id) {
|
||||
calledId = id;
|
||||
});
|
||||
ExtensionManager.markForDisabling(id, true);
|
||||
expect(calledId).toBe(id);
|
||||
expect(ExtensionManager.isMarkedForDisabling(id)).toBe(true);
|
||||
expect(model.filterSet.indexOf(id)).not.toBe(-1);
|
||||
expect(ExtensionManager.hasExtensionsToDisable()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should unmark an extension previously marked for disabling and raise an event", function () {
|
||||
var id = "registered-extension", calledId;
|
||||
runs(function () {
|
||||
ExtensionManager.markForDisabling(id, true);
|
||||
model.on("change", function (e, id) {
|
||||
calledId = id;
|
||||
});
|
||||
ExtensionManager.markForDisabling(id, false);
|
||||
expect(calledId).toBe(id);
|
||||
expect(ExtensionManager.isMarkedForRemoval(id)).toBe(false);
|
||||
expect(ExtensionManager.hasExtensionsToRemove()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable extensions previously marked for disabling and not remove them from the model", function () {
|
||||
var disabledIds = {}, disabledPaths = {};
|
||||
runs(function () {
|
||||
ExtensionManager.markForDisabling("registered-extension", true);
|
||||
ExtensionManager.markForDisabling("Z-capital-extension", false);
|
||||
model.on("change", function (e, id) {
|
||||
disabledIds[id] = true;
|
||||
disabledPaths[disabledFilePath] = true;
|
||||
});
|
||||
waitsForDone(ExtensionManager.disableMarkedExtensions());
|
||||
});
|
||||
runs(function () {
|
||||
// Test the enabled extension, the extension that was unmarked for disabling, and an extension that was never marked.
|
||||
expect(disabledIds["registered-extension"]).toBe(true);
|
||||
expect(disabledPaths["/path/to/extensions/user/registered-extension/.disabled"]).toBe(true);
|
||||
expect(model.filterSet.indexOf("registered-extension")).toBe(0);
|
||||
expect(disabledIds["Z-capital-extension"]).toBeUndefined();
|
||||
expect(disabledPaths["/path/to/extensions/user/Z-capital-extension/.disabled"]).toBeUndefined();
|
||||
expect(disabledIds["unregistered-extension"]).toBeUndefined();
|
||||
expect(disabledPaths["/path/to/extensions/user/unregistered-extension/.disabled"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("should delete the .disabled file, enable the extension and raise an event", function () {
|
||||
var extension = "registered-extension",
|
||||
calledId;
|
||||
runs(function () {
|
||||
mockLoadExtensions(["registered-extension"], "disabled");
|
||||
model.on("change", function (e, id) {
|
||||
calledId = id;
|
||||
});
|
||||
});
|
||||
runs(function () {
|
||||
waitsForDone(ExtensionManager.enable(extension));
|
||||
});
|
||||
runs(function () {
|
||||
expect(calledId).toBe(extension);
|
||||
});
|
||||
});
|
||||
|
||||
it("should mark an extension for update and raise an event", function () {
|
||||
var id = "registered-extension", calledId;
|
||||
runs(function () {
|
||||
@ -1429,26 +1609,35 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should show only items that are already installed and have a remove button for each", function () {
|
||||
it("should show only items that are already installed and have remove and disable/enable buttons for each", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3", "user/mock-extension-4", "user/mock-legacy-extension"]);
|
||||
mockLoadExtensions(["user/mock-extension-5"], "disabled");
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
expect($(".empty-message", view.$el).css("display")).toBe("none");
|
||||
expect($("table", view.$el).css("display")).not.toBe("none");
|
||||
_.forEach(mockRegistry, function (item) {
|
||||
var $button = $("button.remove[data-extension-id=" + item.metadata.name + "]", view.$el);
|
||||
var $removeButton = $("button.remove[data-extension-id=" + item.metadata.name + "]", view.$el);
|
||||
if (item.metadata.name === "mock-extension-3" ||
|
||||
item.metadata.name === "mock-extension-4" ||
|
||||
item.metadata.name === "mock-legacy-extension") {
|
||||
item.metadata.name === "mock-legacy-extension" ||
|
||||
item.metadata.name === "mock-extension-5") {
|
||||
expect(view).toHaveText(item.metadata.name);
|
||||
expect($button.length).toBe(1);
|
||||
expect($removeButton.length).toBe(1);
|
||||
|
||||
// should also have disable/enable button
|
||||
var isDisable = item.metadata.name === "mock-extension-5" ? false : true,
|
||||
$disableButton = $("button." + (isDisable ? "disable" : "enable") + "[data-extension-id=" +
|
||||
item.metadata.name + "]", view.$el);
|
||||
|
||||
expect($disableButton.length).toBe(1);
|
||||
|
||||
// But no update button
|
||||
var $updateButton = $("button.update[data-extension-id=" + item.metadata.name + "]", view.$el);
|
||||
expect($updateButton.length).toBe(0);
|
||||
} else {
|
||||
expect(view).not.toHaveText(item.metadata.name);
|
||||
expect($button.length).toBe(0);
|
||||
expect($removeButton.length).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1515,18 +1704,71 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
// Disable button action
|
||||
it("should mark the given extension for disabling, hide the buttons and show the undo link", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $disableButton = $("button.disable[data-extension-id=mock-extension-3]", view.$el),
|
||||
$removeButton,
|
||||
$undoLink;
|
||||
$disableButton.click();
|
||||
$removeButton = $("button.remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
$undoLink = $("a.undo-disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
$disableButton = $("button.disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect($removeButton.length).toBe(0);
|
||||
expect($undoLink.length).toBe(1);
|
||||
expect($disableButton.length).toBe(0);
|
||||
expect(ExtensionManager.isMarkedForDisabling("mock-extension-3")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should undo mark for disabling and make the buttons available again", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $disableButton = $("button.disable[data-extension-id=mock-extension-3]", view.$el),
|
||||
$removeButton,
|
||||
$undoLink;
|
||||
$disableButton.click();
|
||||
$undoLink = $("a.undo-disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
$undoLink.click();
|
||||
$removeButton = $("button.remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
$disableButton = $("button.disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
$undoLink = $("a.undo-disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect($removeButton.length).toBe(1);
|
||||
expect($undoLink.length).toBe(0);
|
||||
expect($disableButton.length).toBe(1);
|
||||
expect(ExtensionManager.isMarkedForDisabling("mock-extension-3")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to disable an extension from the dev folder", function () {
|
||||
mockLoadExtensions(["dev/mock-extension-6"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $disableButton = $("button.disable[data-extension-id=mock-extension-6]", view.$el);
|
||||
expect($disableButton.prop("disabled")).toBeFalsy();
|
||||
$disableButton.click();
|
||||
expect(ExtensionManager.isMarkedForDisabling("mock-extension-6")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// 'Remove' button action
|
||||
it("should mark the given extension for removal, hide the remove button, and show an undo link", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $button = $("button.remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
$button.click();
|
||||
var $removeButton = $("button.remove[data-extension-id=mock-extension-3]", view.$el),
|
||||
$disableButton;
|
||||
$removeButton.click();
|
||||
$disableButton = $("button.disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect(ExtensionManager.isMarkedForRemoval("mock-extension-3")).toBe(true);
|
||||
expect($disableButton.length).toBe(0);
|
||||
var $undoLink = $("a.undo-remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect($undoLink.length).toBe(1);
|
||||
$button = $("button.remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect($button.length).toBe(0);
|
||||
$removeButton = $("button.remove[data-extension-id=mock-extension-3]", view.$el);
|
||||
expect($removeButton.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1837,6 +2079,34 @@ define(function (require, exports, module) {
|
||||
});
|
||||
});
|
||||
|
||||
it("should not show a disabling confirmation dialog if an extension was marked and then unmarked", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $button = $("button.disable[data-extension-id=mock-extension-3]", view.$el),
|
||||
$undoLink;
|
||||
$button.click();
|
||||
$undoLink = $("a.undo-disable[data-extension-id=mock-extension-3]");
|
||||
$undoLink.click();
|
||||
ExtensionManagerDialog._performChanges();
|
||||
expect(dialogClassShown).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show a disabling confirmation dialog if an extension was marked for disabling", function () {
|
||||
mockLoadExtensions(["user/mock-extension-3"]);
|
||||
setupViewWithMockData(ExtensionManagerViewModel.InstalledViewModel);
|
||||
runs(function () {
|
||||
var $button = $("button.disable[data-extension-id=mock-extension-3]", view.$el);
|
||||
$button.click();
|
||||
});
|
||||
runs(function () {
|
||||
ExtensionManagerDialog._performChanges();
|
||||
expect(dialogClassShown).toBe("change-marked-extensions");
|
||||
$mockDlg.triggerHandler("buttonClick", Dialogs.DIALOG_BTN_CANCEL);
|
||||
});
|
||||
});
|
||||
|
||||
it("should update extensions and quit if the user hits Update and Quit on the removal confirmation dialog", function () {
|
||||
var id = "mock-extension-3",
|
||||
filename = "/path/to/downloaded/mock-extension-3.zip";
|
||||
|
Loading…
Reference in New Issue
Block a user