1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-02-23 06:20:01 +00:00

Deal with dynamic loading and unloading of plugins

More enhancements needed for the test framework too in order to allow events to be processed manually, without relying on the nexttick handler which is awkward in Jasmine
This commit is contained in:
Jeremy Ruston 2025-02-21 09:36:40 +00:00
parent 7e2a8238b5
commit 55c634692b
9 changed files with 350 additions and 156 deletions

View File

@ -1174,8 +1174,8 @@ $tw.Wiki = function(options) {
}
return tiddlerTitles;
},
pluginTiddlersInfo = [], // Array of {tiddler:<tiddler object>,parentPluginTitle:<title>} for all plugins and sub-plugins, ordered by priority
pluginContents = Object.create(null), // Hashmap of parsed plugin content
pluginTiddlersInfo = [], // Array of tiddler fields for all plugins and sub-plugins, ordered by priority
pluginContents = Object.create(null), // Hashmap by title of {contents:<parsed plugin contents>,fields:<fields>,subPluginTitles:<list>}
shadowTiddlerInfo = Object.create(null), // Hashmap by title of {source:, tiddler:}
shadowTiddlerTitles = null, // Array of tiddler titles, calculated and cached when needed
getShadowTiddlerTitles = function() {
@ -1393,8 +1393,8 @@ $tw.Wiki = function(options) {
// Get an array of all the currently recognised plugin types
this.getPluginTypes = function() {
var types = [];
$tw.utils.each(pluginTiddlersInfo,function(pluginTiddlerInfo) {
var pluginType = pluginTiddlerInfo.tiddler.fields["plugin-type"];
$tw.utils.each(pluginTiddlersInfo,function(pluginTiddlerFields) {
var pluginType = pluginTiddlerFields["plugin-type"];
if(pluginType && types.indexOf(pluginType) === -1) {
types.push(pluginType);
}
@ -1402,23 +1402,44 @@ $tw.Wiki = function(options) {
return types;
};
// Read plugin contents for all plugins, or just an array of titles. Returns the list of updated plugin titles and the list of deleted plugin titles.
// Clears the pluginContents cache for any plugins that have been deleted
// Read plugin contents for all plugins, or just an array of titles. Returns the list of updated plugin titles, the list of deleted plugin titles and
// a hashmap of the deleted plugin contents. Clears the pluginContents cache for any plugins that have been deleted
this.readPluginInfo = function(titles) {
var results = {
var self = this,
results = {
modifiedPlugins: [],
deletedPlugins: []
deletedPlugins: [],
deletedPluginContents: {}
};
$tw.utils.each(titles || getTiddlerTitles(),function(title) {
var tiddler = tiddlerStore[title];
var tiddler = self.getTiddler(title);
if(!tiddler) {
for(var t=0; t<pluginContents.length; t++) {
if(pluginContents[t].subPluginTitles.indexOf(title) !== -1) {
tiddler = new $tw.Tiddler(pluginContents[t].fields);
break;
}
}
}
if(tiddler) {
if(tiddler.isPlugin() && tiddler.fields.text) {
var contents = $tw.utils.parseJSONSafe(tiddler.fields.text);
pluginContents[tiddler.fields.title] = contents;
results.modifiedPlugins.push(tiddler.fields.title);
var contents = $tw.utils.parseJSONSafe(tiddler.fields.text).tiddlers;
var subPluginTitles = [];
// Read any sub-plugins
$tw.utils.each(contents,function(subPluginFields,subPluginTitle) {
if(subPluginFields["plugin-type"] && subPluginFields["type"] === "application/json") {
var contents = $tw.utils.parseJSONSafe(subPluginFields.text).tiddlers
pluginContents[subPluginTitle] = {contents: contents,fields: subPluginFields};
results.modifiedPlugins.push(subPluginTitle);
subPluginTitles.push(subPluginTitle)
}
});
pluginContents[title] = {contents: contents,fields: tiddler.fields,subPluginTitles: subPluginTitles};
results.modifiedPlugins.push(title);
}
} else {
if(pluginContents[title]) {
results.deletedPluginContents[title] = {tiddlers: pluginContents[title].contents};
delete pluginContents[title];
results.deletedPlugins.push(title);
}
@ -1427,57 +1448,43 @@ $tw.Wiki = function(options) {
return results;
};
this.isSubPlugin = function(title) {
for(var t=0; t<pluginContents.length; t++) {
if(pluginContents[t].subPluginTitles.indexOf(title) !== -1) {
return true;
}
}
return false;
};
// Get plugin info for a plugin
this.getPluginInfo = function(title) {
return pluginContents[title];
var pluginContent = pluginContents[title];
if(pluginContent) {
return {tiddlers: pluginContent.contents, subPluginTitles: pluginContent.subPluginTitles, fields: pluginContent.fields};
} else {
return null;
}
};
// Register the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
this.registerPluginTiddlers = function(pluginType,titles) {
var self = this,
registeredTitles = [];
if(titles) {
$tw.utils.each(titles,function(title) {
checkTiddler(null,title);
});
} else {
this.each(function(tiddler,title) {
checkTiddler(tiddler,title);
});
}
return registeredTitles;
function checkTiddler(tiddler,title) {
if(!tiddler) {
tiddler = self.getTiddler(title);
}
if(tiddler && tiddler.isPlugin() && (!pluginType || tiddler.fields["plugin-type"] === pluginType)) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlersInfo.push({tiddler: tiddler});
registeredTitles.push(tiddler.fields.title);
// Register any sub-plugins
var contents = pluginContents[tiddler.fields.title];
if(contents) {
$tw.utils.each(contents.tiddlers,function(subPluginFields,subPluginTitle) {
checkSubTiddler(title,subPluginTitle);
});
$tw.utils.each(pluginContents,function(pluginContent,pluginTitle) {
var pluginFields = pluginContent.fields;
if(!titles || titles.indexOf(pluginTitle) !== -1) {
if(!pluginType || pluginFields["plugin-type"] === pluginType) {
var disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + pluginTitle);
if(pluginTitle === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[pluginTitle]); // Unregister the plugin if it's already registered
pluginTiddlersInfo.push(pluginFields);
registeredTitles.push(pluginTitle);
}
}
}
}
function checkSubTiddler(parentPluginTitle,title) {
var tiddlerFields = pluginContents[parentPluginTitle].tiddlers[title];
if(tiddlerFields.type === "application/json" && tiddlerFields["plugin-type"] && !tiddlerFields["draft.of"] && (!pluginType || tiddlerFields["plugin-type"] === pluginType)) {
var tiddler = new $tw.Tiddler(tiddlerFields,{title: title}),
disablingTiddler = self.getTiddler("$:/config/Plugins/Disabled/" + title);
if(title === "$:/core" || !disablingTiddler || (disablingTiddler.fields.text || "").trim() !== "yes") {
self.unregisterPluginTiddlers(null,[title]); // Unregister the plugin if it's already registered
pluginTiddlersInfo.push({tiddler: tiddler,parentPluginTitle: parentPluginTitle});
registeredTitles.push(tiddler.fields.title);
}
}
}
});
return registeredTitles;
};
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
@ -1486,10 +1493,9 @@ $tw.Wiki = function(options) {
unregisteredTitles = [];
// Remove any previous registered plugins of this type
for(var t=pluginTiddlersInfo.length-1; t>=0; t--) {
var pluginInfo = pluginTiddlersInfo[t],
tiddler = pluginInfo.tiddler;
if(tiddler.fields["plugin-type"] && (!pluginType || tiddler.fields["plugin-type"] === pluginType) && (!titles || titles.indexOf(tiddler.fields.title) !== -1)) {
unregisteredTitles.push(tiddler.fields.title);
var pluginFields = pluginTiddlersInfo[t];
if(pluginFields["plugin-type"] && (!pluginType || pluginFields["plugin-type"] === pluginType) && (!titles || titles.indexOf(pluginFields.title) !== -1)) {
unregisteredTitles.push(pluginFields.title);
pluginTiddlersInfo.splice(t,1);
}
}
@ -1501,15 +1507,13 @@ $tw.Wiki = function(options) {
var self = this;
// Sort the plugin titles by the `plugin-priority` field, if this field is missing, default to 1
pluginTiddlersInfo.sort(function(a, b) {
var fieldsA = a.tiddler.fields,
fieldsB = b.tiddler.fields,
priorityA = "plugin-priority" in fieldsA ? fieldsA["plugin-priority"] : 1,
priorityB = "plugin-priority" in fieldsB ? fieldsB["plugin-priority"] : 1;
var priorityA = "plugin-priority" in a ? a["plugin-priority"] : 1,
priorityB = "plugin-priority" in b ? b["plugin-priority"] : 1;
if (priorityA !== priorityB) {
return priorityA - priorityB;
} else if (fieldsA.title < fieldsB.title) {
} else if (a.title < b.title) {
return -1;
} else if (fieldsA.title === fieldsB.title) {
} else if (a.title === b.title) {
return 0;
} else {
return +1;
@ -1517,26 +1521,16 @@ $tw.Wiki = function(options) {
});
// Now go through the plugins in ascending order and assign the shadows
shadowTiddlerInfo = Object.create(null);
$tw.utils.each(pluginTiddlersInfo,function(tiddlerInfo) {
var tiddler = tiddlerInfo.tiddler,
contents;
if(tiddlerInfo.parentPluginTitle) {
contents = $tw.utils.parseJSONSafe(pluginContents[tiddlerInfo.parentPluginTitle].tiddlers[tiddler.fields.title].text || "null");
} else {
contents = pluginContents[tiddler.fields.title];
}
$tw.utils.each(pluginTiddlersInfo,function(tiddlerFields) {
var contents = pluginContents[tiddlerFields.title].contents;
// Extract the constituent tiddlers
if(contents) {
$tw.utils.each(contents.tiddlers,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
if(constituentTitle) {
shadowTiddlerInfo[constituentTitle] = {
source: tiddler.fields.title,
tiddler: new $tw.Tiddler(constituentTiddler,{title: constituentTitle})
};
}
});
}
$tw.utils.each(contents,function(constituentTiddler,constituentTitle) {
// Save the tiddler object
shadowTiddlerInfo[constituentTitle] = {
source: tiddlerFields.title,
tiddler: new $tw.Tiddler(constituentTiddler,{title: constituentTitle})
};
});
});
shadowTiddlerTitles = null; // Force regeneration of the shadow tiddler titles list
this.clearCache(null);

View File

@ -18,68 +18,8 @@ exports.after = ["load-modules"];
exports.before = ["startup"];
exports.synchronous = true;
var TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE = "$:/status/RequireReloadDueToPluginChange";
var PREFIX_CONFIG_REGISTER_PLUGIN_TYPE = "$:/config/RegisterPluginType/";
exports.startup = function() {
$tw.wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "no"});
$tw.wiki.addEventListener("change",function(changes) {
// Work out which of the changed tiddlers are plugins that we need to reregister
var changesToProcess = [],
requireReloadDueToPluginChange = false;
$tw.utils.each(Object.keys(changes),function(title) {
var tiddler = $tw.wiki.getTiddler(title),
requiresReload = $tw.wiki.doesPluginRequireReload(title);
if(requiresReload) {
requireReloadDueToPluginChange = true;
} else if(tiddler) {
var pluginType = tiddler.fields["plugin-type"];
if($tw.wiki.getTiddlerText(PREFIX_CONFIG_REGISTER_PLUGIN_TYPE + (tiddler.fields["plugin-type"] || ""),"no") === "yes") {
changesToProcess.push(title);
}
}
});
// Issue warning if any of the tiddlers require a reload
if(requireReloadDueToPluginChange) {
$tw.wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "yes"});
}
// Read or delete the plugin info of the changed tiddlers
if(changesToProcess.length > 0) {
var changes = $tw.wiki.readPluginInfo(changesToProcess);
if(changes.modifiedPlugins.length > 0 || changes.deletedPlugins.length > 0) {
var changedShadowTiddlers = {};
// Collect the shadow tiddlers of any deleted plugins
$tw.utils.each(changes.deletedPlugins,function(pluginTitle) {
var pluginInfo = $tw.wiki.getPluginInfo(pluginTitle);
if(pluginInfo) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = true;
});
}
});
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changes.modifiedPlugins,function(pluginTitle) {
var pluginInfo = $tw.wiki.getPluginInfo(pluginTitle);
if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
});
}
});
// (Re-)register any modified plugins
$tw.wiki.registerPluginTiddlers(null,changes.modifiedPlugins);
// Unregister any deleted plugins
$tw.wiki.unregisterPluginTiddlers(null,changes.deletedPlugins);
// Unpack the shadow tiddlers
$tw.wiki.unpackPluginTiddlers();
// Queue change events for the changed shadow tiddlers
$tw.utils.each(Object.keys(changedShadowTiddlers),function(title) {
$tw.wiki.enqueueTiddlerEvent(title,changedShadowTiddlers[title], true);
});
}
}
});
$tw.utils.installPluginChangeHandler($tw.wiki);
};
})();

View File

@ -0,0 +1,95 @@
/*\
title: $:/core/modules/utils/plugins.js
type: application/javascript
module-type: utils
Plugin utilities
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE = "$:/status/RequireReloadDueToPluginChange";
var PREFIX_CONFIG_REGISTER_PLUGIN_TYPE = "$:/config/RegisterPluginType/";
exports.installPluginChangeHandler = function(wiki) {
wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "no"});
wiki.addEventListener("change",function(changes) {
// Work out which of the changed tiddlers are plugins that we need to (re)register
var changesToProcess = [];
$tw.utils.each(Object.keys(changes),function(title) {
var tiddler = wiki.getTiddler(title);
if(tiddler) {
// It is a plugin that has been added or modified and is of a type that we need to register
if(tiddler.isPlugin() && $tw.wiki.getTiddlerText(PREFIX_CONFIG_REGISTER_PLUGIN_TYPE + (tiddler.fields["plugin-type"] || ""),"no") === "yes") {
changesToProcess.push(title);
}
} else {
if(wiki.isSubPlugin(title)) {
// It is a sub-plugin
changesToProcess.push(title);
} else {
// It is a plugin that has been deleted
var pluginInfo = wiki.getPluginInfo(title)
if(pluginInfo) {
changesToProcess.push(title);
}
}
}
});
if(changesToProcess.length > 0) {
// Read the plugin info of the changed tiddlers
var changedPluginInfo = wiki.readPluginInfo(changesToProcess);
if(changedPluginInfo.modifiedPlugins.length > 0 || changedPluginInfo.deletedPlugins.length > 0) {
var changedShadowTiddlers = {},
requireReloadDueToPluginChange = false;
// Collect the shadow tiddlers of any deleted plugins
$tw.utils.each(changedPluginInfo.deletedPlugins,function(pluginTitle) {
var contents = changedPluginInfo.deletedPluginContents[pluginTitle];
if(contents && contents.tiddlers) {
$tw.utils.each(Object.keys(contents.tiddlers),function(title) {
changedShadowTiddlers[title] = true;
if(contents.tiddlers[title].type === "application/javascript") {
requireReloadDueToPluginChange = true;
}
});
}
});
// Collect the shadow tiddlers of any modified plugins
$tw.utils.each(changedPluginInfo.modifiedPlugins,function(pluginTitle) {
var pluginInfo = wiki.getPluginInfo(pluginTitle);
if(pluginInfo && pluginInfo.tiddlers) {
$tw.utils.each(Object.keys(pluginInfo.tiddlers),function(title) {
changedShadowTiddlers[title] = false;
if(pluginInfo.tiddlers[title].type === "application/javascript") {
requireReloadDueToPluginChange = true;
}
});
}
});
// (Re-)register any modified plugins
wiki.registerPluginTiddlers(null,changedPluginInfo.modifiedPlugins);
// Unregister any deleted plugins
wiki.unregisterPluginTiddlers(null,changedPluginInfo.deletedPlugins);
// Unpack the shadow tiddlers
wiki.unpackPluginTiddlers();
// Queue change events for the changed shadow tiddlers
$tw.utils.each(changedShadowTiddlers,function(status,title) {
wiki.enqueueTiddlerEvent(title,changedShadowTiddlers[title], true);
});
// Issue warning if any of the tiddlers require a reload
if(requireReloadDueToPluginChange) {
wiki.addTiddler({title: TITLE_REQUIRE_RELOAD_DUE_TO_PLUGIN_CHANGE,text: "yes"});
}
}
}
});
};
})();

View File

@ -135,6 +135,14 @@ exports.dispatchEvent = function(type /*, args */) {
}
};
/*
false (default) to dispatch event tick as usual
true to suppress dispatching the event tick and allow outstanding events to be processed manually
*/
exports.setDispatchMode = function(mode) {
this.dispatchMode = mode;
}
/*
Causes a tiddler to be marked as changed, incrementing the change count, and triggers event handlers.
This method should be called after the changes it describes have been made to the wiki.tiddlers[] array.
@ -159,17 +167,18 @@ exports.enqueueTiddlerEvent = function(title,isDeleted,isShadow) {
}
// Trigger events
this.eventListeners = this.eventListeners || {};
if(!this.eventsTriggered) {
var self = this;
$tw.utils.nextTick(function() {
var changes = self.changedTiddlers;
self.changedTiddlers = Object.create(null);
self.eventsTriggered = false;
if($tw.utils.count(changes) > 0) {
self.dispatchEvent("change",changes);
}
});
this.eventsTriggered = true;
if(!this.eventsTriggered && !this.dispatchMode) {
$tw.utils.nextTick(this.processOutstandingTiddlerEvents.bind(this));
}
this.eventsTriggered = true;
};
exports.processOutstandingTiddlerEvents = function() {
var changes = this.changedTiddlers;
this.changedTiddlers = Object.create(null);
this.eventsTriggered = false;
if($tw.utils.count(changes) > 0) {
this.dispatchEvent("change",changes);
}
};
@ -1743,7 +1752,7 @@ exports.invokeUpgraders = function(titles,tiddlers) {
// Determine whether a plugin by title is dynamically loadable
exports.doesPluginRequireReload = function(title) {
var tiddler = this.getTiddler(title);
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"]) {
if(tiddler && tiddler.isPlugin()) {
if(tiddler.fields["plugin-type"] === "import") {
// The import plugin never requires reloading
return false;

View File

@ -0,0 +1,36 @@
title: Plugins/DynamicJSPluginLoad
description: Dynamic JS plugin loading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/status/RequireReloadDueToPluginChange}}
+
title: Actions
<$action-createtiddler $basetitle="$:/plugins/tiddlywiki/test-js-plugin" $template="$:/plugins/tiddlywiki/test-js-plugin/raw" type="application/json"/>
+
title: $:/plugins/tiddlywiki/test-js-plugin/raw
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: text/plain
{
"tiddlers": {
"$:/plugins/tiddlywiki/test-js-plugin/readme": {
"title": "$:/plugins/tiddlywiki/test-js-plugin/readme",
"text": "Readme from $:/plugins/tiddlywiki/test-js-plugin\n\n"
},
"$:/plugins/tiddlywiki/test-js-plugin/js": {
"title": "$:/plugins/tiddlywiki/test-js-plugin/js",
"text": "2+2",
"type": "application/javascript"
}
}
}
+
title: ExpectedResult
<p>yes</p>

View File

@ -0,0 +1,43 @@
title: Plugins/DynamicSubPluginLoad
description: Dynamic sub-plugin loading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/plugins/tiddlywiki/test-subplugin/readme}}
+
title: Actions
<$action-createtiddler $basetitle="$:/plugins/tiddlywiki/bundled-subplugin-tests" $template="$:/plugins/tiddlywiki/bundled-subplugin-tests/raw" type="application/json"/>
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests/raw
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: text/plain
{
"tiddlers": {
"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme": {
"title": "$:/plugins/tiddlywiki/bundled-subplugin-tests/readme",
"text": "Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"
},
"$:/plugins/tiddlywiki/test-subplugin": {
"title": "$:/plugins/tiddlywiki/test-subplugin",
"name": "test-subplugin",
"description": "Test subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "plugin",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"
}
}
}
+
title: ExpectedResult
<p>Readme from <a class="tc-tiddlylink tc-tiddlylink-shadow" href="#%24%3A%2Fplugins%2Ftiddlywiki%2Ftest-subplugin">$:/plugins/tiddlywiki/test-subplugin</a></p>

View File

@ -0,0 +1,43 @@
title: Plugins/DynamicSubPluginUnload
description: Dynamic sub-plugin unloading
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-specx]]
title: Output
{{$:/plugins/tiddlywiki/bundled-subplugin-tests/readme}}
+
title: Actions
<$action-deletetiddler $tiddler="$:/plugins/tiddlywiki/bundled-subplugin-tests"/>
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: application/json
{
"tiddlers": {
"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme": {
"title": "$:/plugins/tiddlywiki/bundled-subplugin-tests/readme",
"text": "Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"
},
"$:/plugins/tiddlywiki/test-subplugin": {
"title": "$:/plugins/tiddlywiki/test-subplugin",
"name": "test-subplugin",
"description": "Test subplugin",
"list": "readme",
"stability": "STABILITY_1_EXPERIMENTAL",
"version": "5.3.7-prerelease",
"plugin-type": "plugin",
"dependents": "",
"type": "application/json",
"text": "{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"
}
}
}
+
title: ExpectedResult
.

View File

@ -0,0 +1,21 @@
title: Plugins/SimpleSubPlugin
description: Simple sub-plugin
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
{{$:/plugins/tiddlywiki/test-subplugin/readme}}
+
title: $:/plugins/tiddlywiki/bundled-subplugin-tests
list: readme
name: bundled-subplugin-tests
plugin-type: plugin
type: application/json
{"tiddlers":{"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme":{"title":"$:/plugins/tiddlywiki/bundled-subplugin-tests/readme","text":"Readme from $:/plugins/tiddlywiki/bundled-subplugin-tests\n\n"},"$:/plugins/tiddlywiki/test-subplugin":{"title":"$:/plugins/tiddlywiki/test-subplugin","name":"test-subplugin","description":"Test subplugin","list":"readme","stability":"STABILITY_1_EXPERIMENTAL","version":"5.3.7-prerelease","plugin-type":"plugin","dependents":"","type":"application/json","text":"{\"tiddlers\":{\"$:/plugins/tiddlywiki/test-subplugin/readme\":{\"title\":\"$:/plugins/tiddlywiki/test-subplugin/readme\",\"text\":\"Readme from $:/plugins/tiddlywiki/test-subplugin\"},\"$:/plugins/tiddlywiki/test-subsubplugin\":{\"title\":\"$:/plugins/tiddlywiki/test-subsubplugin\",\"name\":\"test-subsubplugin\",\"description\":\"Test subsubplugin\",\"list\":\"readme readme2\",\"stability\":\"STABILITY_1_EXPERIMENTAL\",\"version\":\"5.3.7-prerelease\",\"plugin-type\":\"plugin\",\"dependents\":\"\",\"type\":\"application/json\",\"text\":\"{\\\"tiddlers\\\":{\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\":{\\\"title\\\":\\\"$:/plugins/tiddlywiki/test-subsubplugin/readme\\\",\\\"text\\\":\\\"Readme from $:/plugins/tiddlywiki/test-subsubplugin\\\"}}}\"}}}"}}}
+
title: ExpectedResult
<p>Readme from <a class="tc-tiddlylink tc-tiddlylink-shadow" href="#%24%3A%2Fplugins%2Ftiddlywiki%2Ftest-subplugin">$:/plugins/tiddlywiki/test-subplugin</a></p>

View File

@ -24,11 +24,15 @@ describe("Wiki-based tests", function() {
var tiddler = $tw.wiki.getTiddler(title);
it(tiddler.fields.title + ": " + tiddler.fields.description, function() {
// Add our tiddlers
var wiki = new $tw.Wiki(),
coreTiddler = $tw.wiki.getTiddler("$:/core");
var wiki = new $tw.Wiki();
// Suppress next tick dispatch for wiki change events
wiki.setDispatchMode(true);
// Add the core plugin
var coreTiddler = $tw.wiki.getTiddler("$:/core")
if(coreTiddler) {
wiki.addTiddler(coreTiddler);
}
// Add other tiddlers
wiki.addTiddlers(readMultipleTiddlersTiddler(title));
// Unpack plugin tiddlers
wiki.readPluginInfo();
@ -37,6 +41,8 @@ describe("Wiki-based tests", function() {
wiki.addIndexersToWiki();
// Clear changes queue
wiki.clearTiddlerEventQueue();
// Install the plugin change event handler
$tw.utils.installPluginChangeHandler(wiki);
// Complain if we don't have the ouput and expected results
if(!wiki.tiddlerExists("Output")) {
throw "Missing 'Output' tiddler";
@ -54,7 +60,14 @@ describe("Wiki-based tests", function() {
widgetNode.invokeActionString(wiki.getTiddlerText("Actions"));
refreshWidgetNode(widgetNode,wrapper);
}
// Make sure all wiki events have been cleared
while(wiki.eventsTriggered) {
wiki.processOutstandingTiddlerEvents();
refreshWidgetNode(widgetNode,wrapper);
}
wiki.processOutstandingTiddlerEvents();
// Test the rendering
refreshWidgetNode(widgetNode,wrapper);
expect(wrapper.innerHTML).toBe(wiki.getTiddlerText("ExpectedResult"));
}
});