mirror of
				https://github.com/Jermolene/TiddlyWiki5
				synced 2025-10-30 23:23:02 +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 | ||||
|       - tiddlywiki-com | ||||
| env: | ||||
|   NODE_VERSION: "12" | ||||
|   NODE_VERSION: "18" | ||||
| jobs: | ||||
|   test: | ||||
|     runs-on: ubuntu-latest | ||||
| @@ -14,7 +14,13 @@ jobs: | ||||
|       - uses: actions/setup-node@v1 | ||||
|         with: | ||||
|           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: | ||||
|     runs-on: ubuntu-latest | ||||
|     if: github.ref == 'refs/heads/master' | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,4 +5,6 @@ | ||||
| tmp/ | ||||
| output/ | ||||
| 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) { | ||||
| 	var count = $tw.utils.getInt(operator.operand,1), | ||||
| 		results = []; | ||||
| 	if(count === 0) return results; | ||||
| 	source(function(tiddler,title) { | ||||
| 		results.push(title); | ||||
| 	}); | ||||
|   | ||||
| @@ -823,8 +823,8 @@ exports.hashString = function(str) { | ||||
| Base64 utility functions that work in either browser or Node.js | ||||
| */ | ||||
| if(typeof window !== 'undefined') { | ||||
| 	exports.btoa = window.btoa; | ||||
| 	exports.atob = window.atob; | ||||
| 	exports.btoa = function(binstr) { return window.btoa(binstr); } | ||||
| 	exports.atob = function(b64) { return window.atob(b64); } | ||||
| } else { | ||||
| 	exports.btoa = function(binstr) { | ||||
| 		return Buffer.from(binstr, 'binary').toString('base64'); | ||||
|   | ||||
| @@ -28,6 +28,18 @@ Inherit from the base widget class | ||||
| */ | ||||
| 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 | ||||
| */ | ||||
| @@ -68,8 +80,6 @@ ListWidget.prototype.execute = function() { | ||||
| 	this.counterName = this.getAttribute("counter"); | ||||
| 	this.storyViewName = this.getAttribute("storyview"); | ||||
| 	this.historyTitle = this.getAttribute("history"); | ||||
| 	// Look for <$list-template> and <$list-empty> widgets as immediate child widgets | ||||
| 	this.findExplicitTemplates(); | ||||
| 	// Compose the list elements | ||||
| 	this.list = this.getTiddlerList(); | ||||
| 	var members = [], | ||||
| @@ -92,6 +102,7 @@ ListWidget.prototype.findExplicitTemplates = function() { | ||||
| 	var self = this; | ||||
| 	this.explicitListTemplate = null; | ||||
| 	this.explicitEmptyTemplate = null; | ||||
| 	this.hasTemplateInBody = false; | ||||
| 	var searchChildren = function(childNodes) { | ||||
| 		$tw.utils.each(childNodes,function(node) { | ||||
| 			if(node.type === "list-template") { | ||||
| @@ -100,6 +111,8 @@ ListWidget.prototype.findExplicitTemplates = function() { | ||||
| 				self.explicitEmptyTemplate = node.children; | ||||
| 			} else if(node.type === "element" && node.tag === "p") { | ||||
| 				searchChildren(node.children); | ||||
| 			} else { | ||||
| 				self.hasTemplateInBody = true; | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| @@ -160,11 +173,11 @@ ListWidget.prototype.makeItemTemplate = function(title,index) { | ||||
| 			// Check for a <$list-item> widget | ||||
| 			if(this.explicitListTemplate) { | ||||
| 				templateTree = this.explicitListTemplate; | ||||
| 			} else if (!this.explicitEmptyTemplate) { | ||||
| 			} else if(this.hasTemplateInBody) { | ||||
| 				templateTree = this.parseTreeNode.children; | ||||
| 			} | ||||
| 		} | ||||
| 		if(!templateTree) { | ||||
| 		if(!templateTree || templateTree.length === 0) { | ||||
| 			// 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: [ | ||||
| 				{type: "text", text: title} | ||||
| @@ -414,4 +427,27 @@ ListItemWidget.prototype.refresh = function(changedTiddlers) { | ||||
|  | ||||
| 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 class="tc-edit-field-remove"> | ||||
| <$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}} | ||||
| </$button> | ||||
| </td> | ||||
|   | ||||
| @@ -118,7 +118,7 @@ tags: $:/tags/Macro | ||||
|   <$set name="toc-item-class" filter=<<__itemClassFilter__>> emptyValue="toc-item-selected" value="toc-item" > | ||||
|     <li class=<<toc-item-class>>> | ||||
|       <$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"> | ||||
|             <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> | ||||
|             <$transclude tiddler=<<toc-closed-icon>> /> | ||||
| @@ -145,7 +145,7 @@ tags: $:/tags/Macro | ||||
| <$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"> | ||||
|     <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"> | ||||
|           <$button setTitle=<<toc-state>> setTo="open" class="tc-btn-invisible tc-popup-keep"> | ||||
|             <$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[x]]").join(",")).toBe("$:/ShadowPlugin"); | ||||
| 			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[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"); | ||||
|   | ||||
							
								
								
									
										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; | ||||
| } | ||||
|  | ||||
| .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-gutters {background: <<colour tiddler-editor-background>>; border-right: 1px solid <<colour tiddler-editor-border>>;} | ||||
| .cm-s-tiddlywiki .CodeMirror-linenumber {color: <<colour foreground>>;} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jeremy Ruston
					Jeremy Ruston