diff --git a/boot/boot.css.tid b/boot/boot.css similarity index 96% rename from boot/boot.css.tid rename to boot/boot.css index 27c8884cd..c0d15f1e3 100644 --- a/boot/boot.css.tid +++ b/boot/boot.css @@ -1,6 +1,3 @@ -title: $:/boot/boot.css -type: text/css - /* Basic styles used before we boot up the parsing engine */ diff --git a/boot/boot.js b/boot/boot.js index d2666b32d..d758f04ef 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -142,15 +142,15 @@ $tw.utils.each = function(object,callback) { var next,f,length; if(object) { if(Object.prototype.toString.call(object) == "[object Array]") { - for (f=0, length=object.length; f $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) { + // For the first fifty transcludes we climb up, we simply collect signatures. + // We're assuming that those first 50 will likely include all transcludes involved in the loop. + error.signatures[transcludeSignature] = true; + } else if(!error.signatures[transcludeSignature]) { + // Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering. + this.children = [this.makeChildWidget({type: "error", attributes: { + "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} + }})]; + this.renderChildren(parent,nextSibling); + return; + } + } + throw error; + } }; /* diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 69f63a684..cb8e5e881 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -12,9 +12,6 @@ Widget base class /*global $tw: false */ "use strict"; -/* Maximum permitted depth of the widget tree for recursion detection */ -var MAX_WIDGET_TREE_DEPTH = 1000; - /* Create a widget object for a parse tree node parseTreeNode: reference to the parse tree node to be rendered @@ -494,10 +491,8 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) { this.children = []; var self = this; // Check for too much recursion - if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { - this.children.push(this.makeChildWidget({type: "error", attributes: { - "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} - }})); + if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH) { + throw new $tw.utils.TranscludeRecursionError(); } else { // Create set variable widgets for each variable $tw.utils.each(options.variables,function(value,name) { diff --git a/core/ui/TestCaseTemplate.tid b/core/ui/TestCaseTemplate.tid index 74b6ab27d..9871a2904 100644 --- a/core/ui/TestCaseTemplate.tid +++ b/core/ui/TestCaseTemplate.tid @@ -5,6 +5,7 @@ title: $:/core/ui/TestCaseTemplate <$let linkTarget="yes" displayFormat={{!!display-format}} + testcaseTiddler=<> > <$testcase testOutput="Output" diff --git a/core/ui/TestCases/DefaultTemplate.tid b/core/ui/TestCases/DefaultTemplate.tid index 679620969..3a68253e8 100644 --- a/core/ui/TestCases/DefaultTemplate.tid +++ b/core/ui/TestCases/DefaultTemplate.tid @@ -15,7 +15,7 @@ title: $:/core/ui/testcases/DefaultTemplate

- <$genesis $type={{{ [!match[]then[$link]else[div]] }}}> + <$genesis $type={{{ [!match[]then[$link]else[div]] }}} to=<>> <%if [!match[]] %> !match[fail]then[tc-test-case-result-icon-pass]] [match[fail]then[tc-test-case-result-icon-fail]] +[join[ ]] }}}> <%if [!match[fail]] %> @@ -55,7 +55,9 @@ title: $:/core/ui/testcases/DefaultTemplate
<$view tiddler="Output" format="plainwikified" mode="block"/>
<%else%> <$linkcatcher actions=<>> - <$transclude $tiddler="Output" $mode="block"/> + <$tiddler tiddler="Output"> + <$transclude $tiddler="Output" $mode="block"/> + <%endif%>

diff --git a/editions/test/tiddlers/tests/data/transclude/Recursion.tid b/editions/test/tiddlers/tests/data/transclude/Recursion.tid index d75e671eb..b834f3765 100644 --- a/editions/test/tiddlers/tests/data/transclude/Recursion.tid +++ b/editions/test/tiddlers/tests/data/transclude/Recursion.tid @@ -7,7 +7,8 @@ title: Output \whitespace trim <$transclude $tiddler="Output"/> + + title: ExpectedResult -

Recursive transclusion error in transclude widget

\ No newline at end of file +Recursive transclusion error in transclude widget \ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-plugins.js b/editions/test/tiddlers/tests/test-plugins.js index 29ba4a829..663192a9c 100644 --- a/editions/test/tiddlers/tests/test-plugins.js +++ b/editions/test/tiddlers/tests/test-plugins.js @@ -17,7 +17,7 @@ if($tw.node) { describe("Plugin tests", function() { // Get all the plugins as a hashmap by title of a JSON string with the plugin content - var tiddlers = $tw.utils.getAllPlugins(); + var tiddlers = $tw.utils.getAllPlugins({ignoreEnvironmentVariables: true}); // console.log(JSON.stringify(Object.keys(tiddlers),null,4)); describe("every plugin should have the required standard fields", function() { var titles = Object.keys(tiddlers); diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index 0d1351f31..1c7665a53 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -160,6 +160,47 @@ describe("Widget module", function() { expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); }); + it("should handle single-tiddler recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$tiddler tiddler='TiddlerOne'><$transclude /> <$transclude />"}, + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "tiddler": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget Recursive transclusion error in transclude widget"); + }); + + it("should handle many-tiddler recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/> <$transclude tiddler='TiddlerTwo'/>"}, + {title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"} + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "tiddler": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); + }); + it("should deal with SVG elements", function() { var wiki = new $tw.Wiki(); // Construct the widget node diff --git a/editions/tw5.com/tiddlers/concepts/TestCaseTiddlers.tid b/editions/tw5.com/tiddlers/concepts/TestCaseTiddlers.tid index ff84d6800..cf42c67d7 100644 --- a/editions/tw5.com/tiddlers/concepts/TestCaseTiddlers.tid +++ b/editions/tw5.com/tiddlers/concepts/TestCaseTiddlers.tid @@ -23,5 +23,4 @@ Some payload tiddlers are set aside for special purposes: |''Narrative'' |Narrative description of the test, intended to explain the purpose and operation of the test | |''Output'' |The tiddler that produces the test output | |''~ExpectedResult'' |HTML of expected result of rendering the ''Output'' tiddler | - - +|''Description'' |Set to the text of the <<.field description>> field | diff --git a/editions/tw5.com/tiddlers/testcases/TestCaseWidget/FailingTest.tid b/editions/tw5.com/tiddlers/testcases/TestCaseWidget/FailingTest.tid index bd9126e03..5524a9852 100644 --- a/editions/tw5.com/tiddlers/testcases/TestCaseWidget/FailingTest.tid +++ b/editions/tw5.com/tiddlers/testcases/TestCaseWidget/FailingTest.tid @@ -5,7 +5,7 @@ description: An example of a failing test title: Narrative -This test case intentionally fails to show how failures are displayed. +This test case intentionally fails (in order to show how failures are displayed) + title: Output diff --git a/editions/tw5.com/tiddlers/testcases/TestCaseWidget/NoExpectedResults.tid b/editions/tw5.com/tiddlers/testcases/TestCaseWidget/NoExpectedResults.tid new file mode 100644 index 000000000..a4dcee462 --- /dev/null +++ b/editions/tw5.com/tiddlers/testcases/TestCaseWidget/NoExpectedResults.tid @@ -0,0 +1,12 @@ +title: TestCases/TestCaseWidget/NoExpectedResults +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] +description: A testcase that does not specify expected results + +title: Narrative + +This testcase will display without the pass/fail icons because it does not include an `ExpectedResults` tiddler, and so will only be rendered, and not be executed as a test ++ +title: Output + +This is the output diff --git a/editions/tw5.com/tiddlers/widgets/TestCaseWidget.tid b/editions/tw5.com/tiddlers/widgets/TestCaseWidget.tid index a73403890..608a964d6 100644 --- a/editions/tw5.com/tiddlers/widgets/TestCaseWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/TestCaseWidget.tid @@ -75,7 +75,7 @@ The test case wiki will inherit variables that are visible to the <<.wid testcas A custom template can be specified for special purposes. For example, the provided template $:/core/ui/testcases/RawJSONTemplate just displays the payload tiddlers in JSON, which can be used for debugging purposes. -! Test Czase Template Variables +! Test Case Template Variables The <<.wid testcase>> widget makes the following variables available within the rendered template: diff --git a/languages/zh-Hans/Fields.multids b/languages/zh-Hans/Fields.multids index b406a56ad..50a37b325 100644 --- a/languages/zh-Hans/Fields.multids +++ b/languages/zh-Hans/Fields.multids @@ -30,6 +30,7 @@ name: 具可读性的插件条目的名称 parent-plugin: 对于一个插件,指定其为哪个插件的子插件 plugin-priority: 插件条目的优先级数值 plugin-type: 插件条目的类型 +stability: 插件的开发状态:已弃用、实验性、稳定或旧版 released: TiddlyWiki 的发布日期 revision: 条目存放于服务器中的修订版本 source: 条目的网址 diff --git a/languages/zh-Hant/Fields.multids b/languages/zh-Hant/Fields.multids index a41e8b65e..74e5383a5 100644 --- a/languages/zh-Hant/Fields.multids +++ b/languages/zh-Hant/Fields.multids @@ -30,6 +30,7 @@ name: 具可讀性的套件條目的名稱 parent-plugin: 對於一個插件,指定其為哪個插件的子插件 plugin-priority: 套件條目的優先級數值 plugin-type: 套件條目的類型 +stability: 插件的開發狀態:已棄用、實驗性、穩定或舊版 released: TiddlyWiki 的釋出日期 revision: 條目存放於伺服器中的修訂版本 source: 條目的網址 diff --git a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js index e22fe7378..90d4768e4 100644 --- a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js +++ b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js @@ -34,23 +34,22 @@ describe("Wiki-based tests", function() { if(!wiki.tiddlerExists("Output")) { throw "Missing 'Output' tiddler"; } - if(!wiki.tiddlerExists("ExpectedResult")) { - throw "Missing 'ExpectedResult' tiddler"; + if(wiki.tiddlerExists("ExpectedResult")) { + // Construct the widget node + var text = "{{Output}}\n\n"; + var widgetNode = createWidgetNode(parseText(text,wiki),wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Clear changes queue + wiki.clearTiddlerEventQueue(); + // Run the actions if provided + if(wiki.tiddlerExists("Actions")) { + widgetNode.invokeActionString(wiki.getTiddlerText("Actions")); + refreshWidgetNode(widgetNode,wrapper); + } + // Test the rendering + expect(wrapper.innerHTML).toBe(wiki.getTiddlerText("ExpectedResult")); } - // Construct the widget node - var text = "{{Output}}\n\n"; - var widgetNode = createWidgetNode(parseText(text,wiki),wiki); - // Render the widget node to the DOM - var wrapper = renderWidgetNode(widgetNode); - // Clear changes queue - wiki.clearTiddlerEventQueue(); - // Run the actions if provided - if(wiki.tiddlerExists("Actions")) { - widgetNode.invokeActionString(wiki.getTiddlerText("Actions")); - refreshWidgetNode(widgetNode,wrapper); - } - // Test the rendering - expect(wrapper.innerHTML).toBe(wiki.getTiddlerText("ExpectedResult")); }); });