TiddlyWiki5/editions/test/tiddlers/tests/test-html-parser.js
Jeremy Ruston 6bc77cf3e2
Dynamic parameters for macro/procedure/function calls (#9055)
* Initial commit

The idea is to extend the macro call syntax to accept dynamic parameter values (ie thing:{{more}} etc). Eventually, this will work in all the contexts in which the double angle bracket syntax is valid.

This initial commit gets the tests passing, but doesn't yet activate the new functionality.

* Test for standalone macro calls with dynamic parameters

* Parse attribute macros with the new parser

This fixes the tests

* Test for attribute macros

* Add some examples

* Tweak examples

* Fix test

* Temporarily disable a broken serializer test

* Fix/dynamic macro calls test (#9459)

* Revert "Temporarily disable a broken serializer test"

This reverts commit b3144300ee.

* restore synamic parameter parse result

* lint

* lint

* remove duplicate

* Update core/modules/parsers/parseutils.js

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update editions/test/tiddlers/tests/data/serialize/DynamicWidgetAttribute.tid

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: mixed qouted and unquoted

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix unneeded diff

* Minor docs update

* Genuflecting to the linter

* Remove debug logging

* Add change note

* Allow single closing square brackets within double square brackets quoted strings

* Only allow new style parameter values if the separator is an equals sign

* On reflection, new style values should not be allowed for anonymous parameters

Backwards compatibility

* Docs updates

* Docs updates

---------

Co-authored-by: lin onetwo <linonetwo012@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 11:24:06 +00:00

557 lines
19 KiB
JavaScript

/*\
title: test-html-parser.js
type: application/javascript
tags: [[$:/tags/test-spec]]
Tests for the internal components of the HTML tag parser
\*/
"use strict";
function FakeParser() {
}
$tw.utils.extend(FakeParser.prototype,require("$:/core/modules/parsers/wikiparser/rules/html.js"));
describe("HTML tag new parser tests", function() {
var parser = new FakeParser();
it("should parse whitespace", function() {
expect($tw.utils.parseWhiteSpace("p ",0)).toEqual(
null
);
expect($tw.utils.parseWhiteSpace("p ",1)).toEqual(
{ type : "whitespace", start : 1, end : 3 }
);
});
it("should parse string tokens", function() {
expect($tw.utils.parseTokenString("p= ",0,"=")).toEqual(
null
);
expect($tw.utils.parseTokenString("p= ",1,"=")).toEqual(
{ type : "token", value : "=", start : 1, end : 2 }
);
});
it("should parse regexp tokens", function() {
expect($tw.utils.parseTokenRegExp("p=' ",0,/(=(?:'|"))/)).toEqual(
null
);
expect($tw.utils.parseTokenRegExp("p=' ",1,/(=(?:'|"))/g).match[0]).toEqual(
"='"
);
expect($tw.utils.parseTokenRegExp("p=blah ",2,/([^\s>]+)/g).match[0]).toEqual(
"blah"
);
});
it("should parse string literals", function() {
expect($tw.utils.parseStringLiteral("p='blah' ",0)).toEqual(
null
);
expect($tw.utils.parseStringLiteral("p='blah' ",2)).toEqual(
{ type : "string", start : 2, value : "blah", end : 8 }
);
expect($tw.utils.parseStringLiteral("p='' ",2)).toEqual(
{ type : "string", start : 2, value : "", end : 4 }
);
expect($tw.utils.parseStringLiteral("p=\"blah' ",2)).toEqual(
null
);
expect($tw.utils.parseStringLiteral("p=\"\" ",2)).toEqual(
{ type : "string", start : 2, value : "", end : 4 }
);
});
it("should parse macro parameters", function() {
expect($tw.utils.parseMacroParameter("me",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "me", end : 2 }
);
expect($tw.utils.parseMacroParameter("me:one",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "one", name : "me", end : 6 }
);
expect($tw.utils.parseMacroParameter("me:'one two three'",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "one two three", name : "me", end : 18 }
);
expect($tw.utils.parseMacroParameter("'one two three'",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "one two three", end : 15 }
);
expect($tw.utils.parseMacroParameter("me:[[one two three]]",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "one two three", name : "me", end : 20 }
);
expect($tw.utils.parseMacroParameter("[[one two three]]",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "one two three", end : 17 }
);
expect($tw.utils.parseMacroParameter("myparam>",0)).toEqual(
{ type : "macro-parameter", start : 0, value : "myparam>", end : 8 }
);
});
it("should parse macro invocations", function() {
expect($tw.utils.parseMacroInvocation("<<mymacro",0)).toEqual(
null
);
expect($tw.utils.parseMacroInvocation("<<mymacro>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ ], name : "mymacro", end : 11 }
);
expect($tw.utils.parseMacroInvocation("<<mymacro one two three>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ { type : "macro-parameter", start : 9, value : "one", end : 13 }, { type : "macro-parameter", start : 13, value : "two", end : 17 }, { type : "macro-parameter", start : 17, value : "three", end : 23 } ], name : "mymacro", end : 25 }
);
expect($tw.utils.parseMacroInvocation("<<mymacro p:one q:two three>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ { type : "macro-parameter", start : 9, value : "one", name : "p", end : 15 }, { type : "macro-parameter", start : 15, value : "two", name : "q", end : 21 }, { type : "macro-parameter", start : 21, value : "three", end : 27 } ], name : "mymacro", end : 29 }
);
expect($tw.utils.parseMacroInvocation("<<mymacro 'one two three'>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ { type : "macro-parameter", start : 9, value : "one two three", end : 25 } ], name : "mymacro", end : 27 }
);
expect($tw.utils.parseMacroInvocation("<<mymacro r:'one two three'>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ { type : "macro-parameter", start : 9, value : "one two three", name : "r", end : 27 } ], name : "mymacro", end : 29 }
);
expect($tw.utils.parseMacroInvocation("<<myMacro one:two three:'four and five'>>",0)).toEqual(
{ type : "macrocall", start : 0, params : [ { type : "macro-parameter", start : 9, value : "two", name : "one", end : 17 }, { type : "macro-parameter", start : 17, value : "four and five", name : "three", end : 39 } ], name : "myMacro", end : 41 }
);
});
it("should parse HTML attributes", function() {
expect($tw.utils.parseAttribute("p='blah' ",1)).toEqual(
null
);
expect($tw.utils.parseAttribute("p='blah' ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 8 }
);
expect($tw.utils.parseAttribute("p=\"blah\" ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 8 }
);
expect($tw.utils.parseAttribute("p=\"bl\nah\" ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "bl\nah", end : 9 }
);
expect($tw.utils.parseAttribute("p={{{blah}}} ",0)).toEqual(
{ type : "filtered", start : 0, name : "p", filter : "blah", end : 12 }
);
expect($tw.utils.parseAttribute("p={{{bl\nah}}} ",0)).toEqual(
{ type : "filtered", start : 0, name : "p", filter : "bl\nah", end : 13 }
);
expect($tw.utils.parseAttribute("p={{{ [{$:/layout}] }}} ",0)).toEqual(
{ type : "filtered", start : 0, name : "p", filter : " [{$:/layout}] ", end : 23 }
);
expect($tw.utils.parseAttribute("p={{blah}} ",0)).toEqual(
{ type : "indirect", start : 0, name : "p", textReference : "blah", end : 10 }
);
expect($tw.utils.parseAttribute("p=blah ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 6 }
);
expect($tw.utils.parseAttribute("p =blah ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 7 }
);
expect($tw.utils.parseAttribute("p= blah ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 7 }
);
expect($tw.utils.parseAttribute("p = blah ",0)).toEqual(
{ type : "string", start : 0, name : "p", value : "blah", end : 8 }
);
expect($tw.utils.parseAttribute("p = >blah ",0)).toEqual(
{ type : "string", value : "true", start : 0, name : "p", end : 4 }
);
expect($tw.utils.parseAttribute(" attrib1>",0)).toEqual(
{ type : "string", value : "true", start : 0, name : "attrib1", end : 8 }
);
expect($tw.utils.parseAttribute("p=`blah` ",1)).toEqual(null);
expect($tw.utils.parseAttribute("p=`blah` ",0)).toEqual(
{ start: 0, name: "p", type: "substituted", rawValue: "blah", end: 8 }
);
expect($tw.utils.parseAttribute("p=```blah``` ",0)).toEqual(
{ start: 0, name: "p", type: "substituted", rawValue: "blah", end: 12 }
);
expect($tw.utils.parseAttribute("p=`Hello \"There\"`",0)).toEqual(
{ start: 0, name: "p", type: "substituted", rawValue: 'Hello "There"', end: 17 }
);
});
describe("serializeAttribute", function () {
it("should serialize string attributes", function () {
expect($tw.utils.serializeAttribute({ type: "string", name: "p", value: "blah" })).toBe('p="blah"');
expect($tw.utils.serializeAttribute({ type: "string", name: "p", value: "true" })).toBe("p");
});
it("should serialize filtered attributes", function () {
expect($tw.utils.serializeAttribute({ type: "filtered", name: "p", filter: "blah" })).toBe("p={{{blah}}}");
});
it("should serialize indirect attributes", function () {
expect($tw.utils.serializeAttribute({ type: "indirect", name: "p", textReference: "blah" })).toBe("p={{blah}}");
});
it("should serialize substituted attributes", function () {
expect($tw.utils.serializeAttribute({ type: "substituted", name: "p", rawValue: "blah" })).toBe("p=`blah`");
});
it("should return null for unsupported types", function () {
expect($tw.utils.serializeAttribute({ type: "unknown", name: "p", value: "blah" })).toBeNull();
});
it("should return null for invalid input", function () {
expect($tw.utils.serializeAttribute(null)).toBeNull();
expect($tw.utils.serializeAttribute({})).toBeNull();
expect($tw.utils.serializeAttribute({ type: "string" })).toBeNull();
expect($tw.utils.serializeAttribute({ name: "p" })).toBeNull();
});
});
it("should parse HTML tags", function() {
expect(parser.parseTag("<mytag>",1)).toEqual(
null
);
expect(parser.parseTag("</mytag>",0)).toEqual(
null
);
expect(parser.parseTag("<mytag>",0)).toEqual(
{ type : "element", start : 0, attributes : { }, orderedAttributes: [ ], tag : "mytag", end : 7 }
);
expect(parser.parseTag("<mytag attrib1>",0)).toEqual(
{ type : "element", start : 0, attributes : { attrib1 : { type : "string", value : "true", start : 6, name : "attrib1", end : 14 } }, orderedAttributes: [ { start: 6, name: "attrib1", type: "string", value: "true", end: 14 } ], tag : "mytag", end : 15 }
);
expect(parser.parseTag("<mytag attrib1/>",0)).toEqual(
{ type : "element", start : 0, attributes : { attrib1 : { type : "string", value : "true", start : 6, name : "attrib1", end : 14 } }, orderedAttributes: [ { start: 6, name: "attrib1", type: "string", value: "true", end: 14 } ], tag : "mytag", isSelfClosing : true, end : 16 }
);
expect(parser.parseTag("<$view field=\"title\" format=\"link\"/>",0)).toEqual(
{ type : "view", start : 0, attributes : { field : { start : 6, name : "field", type : "string", value : "title", end : 20 }, format : { start : 20, name : "format", type : "string", value : "link", end : 34 } }, orderedAttributes: [ { start: 6, name: "field", type: "string", value: "title", end: 20 }, { start: 20, name: "format", type: "string", value: "link", end: 34 } ], tag : "$view", isSelfClosing : true, end : 36 }
);
expect(parser.parseTag("<mytag attrib1='something'>",0)).toEqual(
{ type : "element", start : 0, attributes : { attrib1 : { type : "string", start : 6, name : "attrib1", value : "something", end : 26 } }, orderedAttributes: [ { start: 6, name: "attrib1", type: "string", value: "something", end: 26 } ], tag : "mytag", end : 27 }
);
expect(parser.parseTag("<mytag attrib1 attrib1='something'>",0)).toEqual(
{ type : "element", start : 0, attributes : { attrib1 : { type : "string", start : 15, name : "attrib1", value : "something", end : 34 } }, orderedAttributes: [ { start: 6, name: "attrib1", type: "string", value: "true", end: 15 }, { start: 15, name: "attrib1", type: "string", value: "something", end: 34 } ], tag : "mytag", end : 35 }
);
expect(parser.parseTag("<mytag attrib1 attrib1='something' attrib1='else'>",0)).toEqual(
{ type : "element", start : 0, attributes : { attrib1 : { type : "string", start : 34, name : "attrib1", value : "else", end : 49 } }, orderedAttributes: [ { start: 6, name: "attrib1", type: "string", value: "true", end: 15 }, { start: 15, name: "attrib1", type: "string", value: "something", end: 34 }, { start: 34, name: "attrib1", type: "string", value: "else", end: 49 } ], tag : "mytag", end : 50 }
);
expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing>",0)).toEqual(
{ type : "mytag", start : 0, attributes : { attrib1 : { type : "string", start : 7, name : "attrib1", value : "something", end : 27 }, attrib2 : { type : "string", start : 27, name : "attrib2", value : "else", end : 40 }, thing : { type : "string", start : 40, name : "thing", value : "true", end : 46 } }, orderedAttributes: [ { start: 7, name: "attrib1", type: "string", value: "something", end: 27 }, { start: 27, name: "attrib2", type: "string", value: "else", end: 40 }, { start: 40, name: "thing", type: "string", value: "true", end: 46 } ], tag : "$mytag", end : 47 }
);
expect(parser.parseTag("< $mytag attrib1='something' attrib2=else thing>",0)).toEqual(
null
);
expect(parser.parseTag("<$mytag attrib3=<<myMacro one:two three:'four and five'>>>", 0)).toEqual(
{
"type": "mytag",
"start": 0,
"attributes": {
"attrib3": {
"start": 7,
"name": "attrib3",
"type": "macro",
"value": {
"type": "transclude",
"start": 16,
"attributes": {
"$variable": {
"name": "$variable",
"type": "string",
"value": "myMacro"
},
"one": {
"start": 25,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 33
},
"three": {
"start": 33,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 55
}
},
"orderedAttributes": [
{
"name": "$variable",
"type": "string",
"value": "myMacro"
},
{
"start": 25,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 33
},
{
"start": 33,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 55
}
],
"end": 57
},
"end": 57
}
},
"orderedAttributes": [
{
"start": 7,
"name": "attrib3",
"type": "macro",
"value": {
"type": "transclude",
"start": 16,
"attributes": {
"$variable": {
"name": "$variable",
"type": "string",
"value": "myMacro"
},
"one": {
"start": 25,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 33
},
"three": {
"start": 33,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 55
}
},
"orderedAttributes": [
{
"name": "$variable",
"type": "string",
"value": "myMacro"
},
{
"start": 25,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 33
},
{
"start": 33,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 55
}
],
"end": 57
},
"end": 57
}
],
"tag": "$mytag",
"end": 58
}
);
expect(parser.parseTag("<$mytag attrib1='something' attrib2=else thing attrib3=<<myMacro one:two three:'four and five'>>>",0)).toEqual(
{
"type": "mytag",
"start": 0,
"attributes": {
"attrib1": {
"start": 7,
"name": "attrib1",
"type": "string",
"value": "something",
"end": 27
},
"attrib2": {
"start": 27,
"name": "attrib2",
"type": "string",
"value": "else",
"end": 40
},
"thing": {
"start": 40,
"name": "thing",
"type": "string",
"value": "true",
"end": 47
},
"attrib3": {
"start": 47,
"name": "attrib3",
"type": "macro",
"value": {
"type": "transclude",
"start": 55,
"attributes": {
"$variable": {
"name": "$variable",
"type": "string",
"value": "myMacro"
},
"one": {
"start": 64,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 72
},
"three": {
"start": 72,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 94
}
},
"orderedAttributes": [
{
"name": "$variable",
"type": "string",
"value": "myMacro"
},
{
"start": 64,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 72
},
{
"start": 72,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 94
}
],
"end": 96
},
"end": 96
}
},
"orderedAttributes": [
{
"start": 7,
"name": "attrib1",
"type": "string",
"value": "something",
"end": 27
},
{
"start": 27,
"name": "attrib2",
"type": "string",
"value": "else",
"end": 40
},
{
"start": 40,
"name": "thing",
"type": "string",
"value": "true",
"end": 47
},
{
"start": 47,
"name": "attrib3",
"type": "macro",
"value": {
"type": "transclude",
"start": 55,
"attributes": {
"$variable": {
"name": "$variable",
"type": "string",
"value": "myMacro"
},
"one": {
"start": 64,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 72
},
"three": {
"start": 72,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 94
}
},
"orderedAttributes": [
{
"name": "$variable",
"type": "string",
"value": "myMacro"
},
{
"start": 64,
"name": "one",
"assignmentOperator": ":",
"type": "string",
"value": "two",
"end": 72
},
{
"start": 72,
"name": "three",
"assignmentOperator": ":",
"type": "string",
"value": "four and five",
"quoted": true,
"end": 94
}
],
"end": 96
},
"end": 96
}
],
"tag": "$mytag",
"end": 97
}
);
});
it("should find and parse HTML tags", function() {
expect(parser.findNextTag("<something <mytag>",1)).toEqual(
{ type : "element", start : 11, attributes : { }, orderedAttributes: [ ], tag : "mytag", end : 18 }
);
expect(parser.findNextTag("something else </mytag>",0)).toEqual(
null
);
expect(parser.findNextTag("<<some other stuff>> <mytag>",0)).toEqual(
{ type : "element", start : 1, attributes : { other : { type : "string", value : "true", start : 6, name : "other", end : 13 }, stuff : { type : "string", value : "true", start : 13, name : "stuff", end : 18 } }, orderedAttributes: [ { type : "string", value : "true", start : 6, name : "other", end : 13 }, { type : "string", value : "true", start : 13, name : "stuff", end : 18 } ], tag : "some", end : 19 }
);
expect(parser.findNextTag("<<some other stuff>> <mytag>",2)).toEqual(
{ type : "element", start : 21, attributes : { }, orderedAttributes: [ ], tag : "mytag", end : 28 }
);
});
});