mirror of
				https://github.com/Jermolene/TiddlyWiki5
				synced 2025-10-30 15:13:00 +00:00 
			
		
		
		
	Merge branch 'master' into demo-alternate-store
This commit is contained in:
		
							
								
								
									
										10
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,7 +5,7 @@ on: | |||||||
|       - master |       - master | ||||||
|       - tiddlywiki-com |       - tiddlywiki-com | ||||||
| env: | env: | ||||||
|   NODE_VERSION: "12" |   NODE_VERSION: "18" | ||||||
| jobs: | jobs: | ||||||
|   test: |   test: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
| @@ -14,7 +14,13 @@ jobs: | |||||||
|       - uses: actions/setup-node@v1 |       - uses: actions/setup-node@v1 | ||||||
|         with: |         with: | ||||||
|           node-version: "${{ env.NODE_VERSION }}" |           node-version: "${{ env.NODE_VERSION }}" | ||||||
|       - run: "./bin/test.sh" |       - run: "./bin/ci-test.sh" | ||||||
|  |       - uses: actions/upload-artifact@v3 | ||||||
|  |         if: always() | ||||||
|  |         with: | ||||||
|  |           name: playwright-report | ||||||
|  |           path: playwright-report/ | ||||||
|  |           retention-days: 30 | ||||||
|   build-prerelease: |   build-prerelease: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     if: github.ref == 'refs/heads/master' |     if: github.ref == 'refs/heads/master' | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,4 +5,6 @@ | |||||||
| tmp/ | tmp/ | ||||||
| output/ | output/ | ||||||
| node_modules/ | node_modules/ | ||||||
|  | /test-results/ | ||||||
|  | /playwright-report/ | ||||||
|  | /playwright/.cache/ | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								bin/ci-test.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								bin/ci-test.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # test TiddlyWiki5 for tiddlywiki.com | ||||||
|  |  | ||||||
|  | node ./tiddlywiki.js \ | ||||||
|  | 	./editions/test \ | ||||||
|  | 	--verbose \ | ||||||
|  | 	--version \ | ||||||
|  | 	--rendertiddler $:/core/save/all test.html text/plain \ | ||||||
|  | 	--test \ | ||||||
|  | 	|| exit 1 | ||||||
|  |  | ||||||
|  | npm install playwright @playwright/test | ||||||
|  | npx playwright install chromium firefox --with-deps | ||||||
|  |  | ||||||
|  | npx playwright test | ||||||
| @@ -58,6 +58,7 @@ Last entry/entries in list | |||||||
| exports.last = function(source,operator,options) { | exports.last = function(source,operator,options) { | ||||||
| 	var count = $tw.utils.getInt(operator.operand,1), | 	var count = $tw.utils.getInt(operator.operand,1), | ||||||
| 		results = []; | 		results = []; | ||||||
|  | 	if(count === 0) return results; | ||||||
| 	source(function(tiddler,title) { | 	source(function(tiddler,title) { | ||||||
| 		results.push(title); | 		results.push(title); | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
| @@ -823,8 +823,8 @@ exports.hashString = function(str) { | |||||||
| Base64 utility functions that work in either browser or Node.js | Base64 utility functions that work in either browser or Node.js | ||||||
| */ | */ | ||||||
| if(typeof window !== 'undefined') { | if(typeof window !== 'undefined') { | ||||||
| 	exports.btoa = window.btoa; | 	exports.btoa = function(binstr) { return window.btoa(binstr); } | ||||||
| 	exports.atob = window.atob; | 	exports.atob = function(b64) { return window.atob(b64); } | ||||||
| } else { | } else { | ||||||
| 	exports.btoa = function(binstr) { | 	exports.btoa = function(binstr) { | ||||||
| 		return Buffer.from(binstr, 'binary').toString('base64'); | 		return Buffer.from(binstr, 'binary').toString('base64'); | ||||||
|   | |||||||
| @@ -28,6 +28,18 @@ Inherit from the base widget class | |||||||
| */ | */ | ||||||
| ListWidget.prototype = new Widget(); | ListWidget.prototype = new Widget(); | ||||||
|  |  | ||||||
|  | ListWidget.prototype.initialise = function(parseTreeNode,options) { | ||||||
|  | 	// Bail if parseTreeNode is undefined, meaning that the ListWidget constructor was called without any arguments so that it can be subclassed | ||||||
|  | 	if(parseTreeNode === undefined) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	// First call parent constructor to set everything else up | ||||||
|  | 	Widget.prototype.initialise.call(this,parseTreeNode,options); | ||||||
|  | 	// Now look for <$list-template> and <$list-empty> widgets as immediate child widgets | ||||||
|  | 	// This is safe to do during initialization because parse trees never change after creation | ||||||
|  | 	this.findExplicitTemplates(); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| Render this widget into the DOM | Render this widget into the DOM | ||||||
| */ | */ | ||||||
| @@ -68,8 +80,6 @@ ListWidget.prototype.execute = function() { | |||||||
| 	this.counterName = this.getAttribute("counter"); | 	this.counterName = this.getAttribute("counter"); | ||||||
| 	this.storyViewName = this.getAttribute("storyview"); | 	this.storyViewName = this.getAttribute("storyview"); | ||||||
| 	this.historyTitle = this.getAttribute("history"); | 	this.historyTitle = this.getAttribute("history"); | ||||||
| 	// Look for <$list-template> and <$list-empty> widgets as immediate child widgets |  | ||||||
| 	this.findExplicitTemplates(); |  | ||||||
| 	// Compose the list elements | 	// Compose the list elements | ||||||
| 	this.list = this.getTiddlerList(); | 	this.list = this.getTiddlerList(); | ||||||
| 	var members = [], | 	var members = [], | ||||||
| @@ -92,6 +102,7 @@ ListWidget.prototype.findExplicitTemplates = function() { | |||||||
| 	var self = this; | 	var self = this; | ||||||
| 	this.explicitListTemplate = null; | 	this.explicitListTemplate = null; | ||||||
| 	this.explicitEmptyTemplate = null; | 	this.explicitEmptyTemplate = null; | ||||||
|  | 	this.hasTemplateInBody = false; | ||||||
| 	var searchChildren = function(childNodes) { | 	var searchChildren = function(childNodes) { | ||||||
| 		$tw.utils.each(childNodes,function(node) { | 		$tw.utils.each(childNodes,function(node) { | ||||||
| 			if(node.type === "list-template") { | 			if(node.type === "list-template") { | ||||||
| @@ -100,6 +111,8 @@ ListWidget.prototype.findExplicitTemplates = function() { | |||||||
| 				self.explicitEmptyTemplate = node.children; | 				self.explicitEmptyTemplate = node.children; | ||||||
| 			} else if(node.type === "element" && node.tag === "p") { | 			} else if(node.type === "element" && node.tag === "p") { | ||||||
| 				searchChildren(node.children); | 				searchChildren(node.children); | ||||||
|  | 			} else { | ||||||
|  | 				self.hasTemplateInBody = true; | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
| @@ -160,11 +173,11 @@ ListWidget.prototype.makeItemTemplate = function(title,index) { | |||||||
| 			// Check for a <$list-item> widget | 			// Check for a <$list-item> widget | ||||||
| 			if(this.explicitListTemplate) { | 			if(this.explicitListTemplate) { | ||||||
| 				templateTree = this.explicitListTemplate; | 				templateTree = this.explicitListTemplate; | ||||||
| 			} else if (!this.explicitEmptyTemplate) { | 			} else if(this.hasTemplateInBody) { | ||||||
| 				templateTree = this.parseTreeNode.children; | 				templateTree = this.parseTreeNode.children; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if(!templateTree) { | 		if(!templateTree || templateTree.length === 0) { | ||||||
| 			// Default template is a link to the title | 			// Default template is a link to the title | ||||||
| 			templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ | 			templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [ | ||||||
| 				{type: "text", text: title} | 				{type: "text", text: title} | ||||||
| @@ -414,4 +427,27 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) { | |||||||
|  |  | ||||||
| exports.listitem = ListItemWidget; | exports.listitem = ListItemWidget; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | Make <$list-template> and <$list-empty> widgets that do nothing | ||||||
|  | */ | ||||||
|  | var ListTemplateWidget = function(parseTreeNode,options) { | ||||||
|  | 	// Main initialisation inherited from widget.js | ||||||
|  | 	this.initialise(parseTreeNode,options); | ||||||
|  | }; | ||||||
|  | ListTemplateWidget.prototype = new Widget(); | ||||||
|  | ListTemplateWidget.prototype.render = function() {} | ||||||
|  | ListTemplateWidget.prototype.refresh = function() { return false; } | ||||||
|  |  | ||||||
|  | exports["list-template"] = ListTemplateWidget; | ||||||
|  |  | ||||||
|  | var ListEmptyWidget = function(parseTreeNode,options) { | ||||||
|  | 	// Main initialisation inherited from widget.js | ||||||
|  | 	this.initialise(parseTreeNode,options); | ||||||
|  | }; | ||||||
|  | ListEmptyWidget.prototype = new Widget(); | ||||||
|  | ListEmptyWidget.prototype.render = function() {} | ||||||
|  | ListEmptyWidget.prototype.refresh = function() { return false; } | ||||||
|  |  | ||||||
|  | exports["list-empty"] = ListEmptyWidget; | ||||||
|  |  | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -89,7 +89,7 @@ $value={{{ [subfilter<get-field-value-tiddler-filter>get[text]] }}}/> | |||||||
| </td> | </td> | ||||||
| <td class="tc-edit-field-remove"> | <td class="tc-edit-field-remove"> | ||||||
| <$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}> | <$button class="tc-btn-invisible" tooltip={{$:/language/EditTemplate/Field/Remove/Hint}} aria-label={{$:/language/EditTemplate/Field/Remove/Caption}}> | ||||||
| <$action-deletefield $field=<<currentField>>/><$set name="currentTiddlerCSSescaped" value={{{ [<currentTiddler>escapecss[]] }}}><$action-sendmessage $message="tm-focus-selector" $param=<<current-tiddler-new-field-selector>>/></$set> | <$action-deletefield $field=<<currentField>>/> | ||||||
| {{$:/core/images/delete-button}} | {{$:/core/images/delete-button}} | ||||||
| </$button> | </$button> | ||||||
| </td> | </td> | ||||||
|   | |||||||
| @@ -118,7 +118,7 @@ tags: $:/tags/Macro | |||||||
|   <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" > |   <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" > | ||||||
|     <li class=<<toc-item-class>>> |     <li class=<<toc-item-class>>> | ||||||
|       <$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}> |       <$link to={{{ [<currentTiddler>get[target]else<currentTiddler>] }}}> | ||||||
|           <$list filter="[all[current]tagging[]$sort$limit[1]]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>"> |           <$list filter="[all[current]tagging[]$sort$limit[1]]  -[subfilter<__exclude__>]" variable="ignore" emptyMessage="<$button class='tc-btn-invisible'>{{$:/core/images/blank}}</$button>"> | ||||||
|           <$reveal type="nomatch" stateTitle=<<toc-state>> text="open"> |           <$reveal type="nomatch" stateTitle=<<toc-state>> text="open"> | ||||||
|             <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> |             <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> | ||||||
|             <$transclude tiddler=<<toc-closed-icon>> /> |             <$transclude tiddler=<<toc-closed-icon>> /> | ||||||
| @@ -145,7 +145,7 @@ tags: $:/tags/Macro | |||||||
| <$qualify name="toc-state" title={{{ [[$:/state/toc]addsuffix<__path__>addsuffix[-]addsuffix<currentTiddler>] }}}> | <$qualify name="toc-state" title={{{ [[$:/state/toc]addsuffix<__path__>addsuffix[-]addsuffix<currentTiddler>] }}}> | ||||||
|   <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item"> |   <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item"> | ||||||
|     <li class=<<toc-item-class>>> |     <li class=<<toc-item-class>>> | ||||||
|       <$list filter="[all[current]tagging[]$sort$limit[1]]" variable="ignore" emptyMessage="""<$button class="tc-btn-invisible">{{$:/core/images/blank}}</$button><span class="toc-item-muted"><<toc-caption>></span>"""> |       <$list filter="[all[current]tagging[]$sort$limit[1]] -[subfilter<__exclude__>]" variable="ignore" emptyMessage="""<$button class="tc-btn-invisible">{{$:/core/images/blank}}</$button><span class="toc-item-muted"><<toc-caption>></span>"""> | ||||||
|         <$reveal type="nomatch" stateTitle=<<toc-state>> text="open"> |         <$reveal type="nomatch" stateTitle=<<toc-state>> text="open"> | ||||||
|           <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> |           <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> | ||||||
|             <$transclude tiddler=<<toc-closed-icon>> /> |             <$transclude tiddler=<<toc-closed-icon>> /> | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								editions/test/playwright.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								editions/test/playwright.spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | const { test, expect } = require('@playwright/test'); | ||||||
|  | const {resolve} = require('path'); | ||||||
|  |  | ||||||
|  | const indexPath = resolve(__dirname, 'output', 'test.html'); | ||||||
|  | const crossPlatformIndexPath = indexPath.replace(/^\/+/, ''); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | test('get started link', async ({ page }) => { | ||||||
|  |     // The tests can take a while to run | ||||||
|  |     const timeout = 1000 * 30; | ||||||
|  |     test.setTimeout(timeout); | ||||||
|  |  | ||||||
|  |     // Load the generated test TW html | ||||||
|  |     await page.goto(`file:///${crossPlatformIndexPath}`); | ||||||
|  |  | ||||||
|  |     // Sanity check | ||||||
|  |     await expect(page.locator('.tc-site-title'), "Expected correct page title to verify the test page was loaded").toHaveText('TiddlyWiki5'); | ||||||
|  |  | ||||||
|  |     // Wait for jasmine results bar to appear | ||||||
|  |     await expect(page.locator('.jasmine-overall-result'), "Expected jasmine test results bar to be present").toBeVisible({timeout}); | ||||||
|  |  | ||||||
|  |     // Assert the tests have passed | ||||||
|  |     await expect(page.locator('.jasmine-overall-result.jasmine-failed'), "Expected jasmine tests to not have failed").not.toBeVisible(); | ||||||
|  |     await expect(page.locator('.jasmine-overall-result.jasmine-passed'), "Expected jasmine tests to have passed").toBeVisible(); | ||||||
|  | }); | ||||||
| @@ -365,6 +365,7 @@ Tests the filtering mechanism. | |||||||
| 			expect(wiki.filterTiddlers("[sort[title]first[8]]").join(",")).toBe("$:/ShadowPlugin,$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three"); | 			expect(wiki.filterTiddlers("[sort[title]first[8]]").join(",")).toBe("$:/ShadowPlugin,$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three"); | ||||||
| 			expect(wiki.filterTiddlers("[sort[title]first[x]]").join(",")).toBe("$:/ShadowPlugin"); | 			expect(wiki.filterTiddlers("[sort[title]first[x]]").join(",")).toBe("$:/ShadowPlugin"); | ||||||
| 			expect(wiki.filterTiddlers("[sort[title]last[]]").join(",")).toBe("TiddlerOne"); | 			expect(wiki.filterTiddlers("[sort[title]last[]]").join(",")).toBe("TiddlerOne"); | ||||||
|  | 			expect(wiki.filterTiddlers("[sort[title]last[0]]").join(",")).toBe(""); | ||||||
| 			expect(wiki.filterTiddlers("[sort[title]last[2]]").join(",")).toBe("Tiddler Three,TiddlerOne"); | 			expect(wiki.filterTiddlers("[sort[title]last[2]]").join(",")).toBe("Tiddler Three,TiddlerOne"); | ||||||
| 			expect(wiki.filterTiddlers("[sort[title]last[8]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three,TiddlerOne"); | 			expect(wiki.filterTiddlers("[sort[title]last[8]]").join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,filter regexp test,has filter,hasList,one,Tiddler Three,TiddlerOne"); | ||||||
| 			expect(wiki.filterTiddlers("[sort[title]last[x]]").join(",")).toBe("TiddlerOne"); | 			expect(wiki.filterTiddlers("[sort[title]last[x]]").join(",")).toBe("TiddlerOne"); | ||||||
|   | |||||||
							
								
								
									
										46
									
								
								playwright.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								playwright.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | const { defineConfig, devices } = require('@playwright/test'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @see https://playwright.dev/docs/test-configuration | ||||||
|  |  */ | ||||||
|  | module.exports = defineConfig({ | ||||||
|  |   testDir: './editions/test/', | ||||||
|  |  | ||||||
|  |   // Allow parallel tests | ||||||
|  |   fullyParallel: true, | ||||||
|  |  | ||||||
|  |   // Prevent accidentally committed "test.only" from wrecking havoc | ||||||
|  |   forbidOnly: !!process.env.CI, | ||||||
|  |  | ||||||
|  |   // Do not retry tests on failure | ||||||
|  |   retries: 0, | ||||||
|  |  | ||||||
|  |   // How many parallel workers | ||||||
|  |   workers: process.env.CI ? 1 : undefined, | ||||||
|  |  | ||||||
|  |   // Reporter to use. See https://playwright.dev/docs/test-reporters | ||||||
|  |   reporter: 'html', | ||||||
|  |  | ||||||
|  |   // Settings shared with all the tests | ||||||
|  |   use: { | ||||||
|  |     // Take a screenshot when the test fails | ||||||
|  |     screenshot: { | ||||||
|  |       mode: 'only-on-failure', | ||||||
|  |       fullPage: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   /* Configure projects for major browsers */ | ||||||
|  |   projects: [ | ||||||
|  |     { | ||||||
|  |       name: 'chromium', | ||||||
|  |       use: { ...devices['Desktop Chrome'] }, | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     { | ||||||
|  |       name: 'firefox', | ||||||
|  |       use: { ...devices['Desktop Firefox'] }, | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  | }); | ||||||
|  |  | ||||||
| @@ -56,6 +56,11 @@ name: tiddlywiki | |||||||
|   rendering-intent: auto; |   rendering-intent: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .tc-tiddler-frame .tc-tiddler-editor .tc-edit-texteditor, | ||||||
|  | .tc-tiddler-frame .tc-tiddler-editor .tc-tiddler-preview-preview { | ||||||
|  | 	overflow: auto; | ||||||
|  | } | ||||||
|  |  | ||||||
| .cm-s-tiddlywiki.CodeMirror, .cm-s-tiddlywiki .CodeMirror-gutters { background-color: <<colour tiddler-editor-background>>; color: <<colour foreground>>; } | .cm-s-tiddlywiki.CodeMirror, .cm-s-tiddlywiki .CodeMirror-gutters { background-color: <<colour tiddler-editor-background>>; color: <<colour foreground>>; } | ||||||
| .cm-s-tiddlywiki .CodeMirror-gutters {background: <<colour tiddler-editor-background>>; border-right: 1px solid <<colour tiddler-editor-border>>;} | .cm-s-tiddlywiki .CodeMirror-gutters {background: <<colour tiddler-editor-background>>; border-right: 1px solid <<colour tiddler-editor-border>>;} | ||||||
| .cm-s-tiddlywiki .CodeMirror-linenumber {color: <<colour foreground>>;} | .cm-s-tiddlywiki .CodeMirror-linenumber {color: <<colour foreground>>;} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jeremy Ruston
					Jeremy Ruston