Commit 0960a0b8 authored by Leon Arnott's avatar Leon Arnott
Browse files

Added (checkbox-fullscreen:) and (verbatim-source:).

These were two minor variations of other macros I wanted to throw in for completeness' sake.

Also fixed a bug in the syntax highlighter related to link text, and corrected a few tooltips.
parent 2a5ad37f5cbb
......@@ -214,9 +214,11 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Added a `(hide:)` macro, which removes the contents of a given hook from the passage, but allows the `(show:)` macro to restore the contents later. Hooks hidden with `(hide:)` will not re-run any containing macros when `(show:)` is used on them later.
* Added a `(seq-link:)` macro, a variation of `(cycling-link:)` which does not cycle - it simply turns into plain text on the final string.
* Added a `(verbatim-print:)` command macro (alias `(v6m-print:)`), a combination of `(verbatim:)` and `(print:)`. This is designed especially for printing player-inputted strings that may, for whatever reason, contain markup.
* Added a `(verbatim-source:)` command macro (alias `(v6m-source:)`), a combination of `(verbatim-print:)` and `(source:)`, intended for debugging.
* Added a debugging macro, `(ignore:)`. If you wish to temporarily disable a command in the passage you're currently testing, simply change its name to "ignore" and it will ignore all of the values given to it, without causing an error.
* Added `(click-undo:)`, `(mouseover-undo:)`, and `(mouseout-undo:)`, to complement `(click-goto:)` and the rest.
* Added `(link-fullscreen:)`, a command macro that creates a link that, when clicked, toggles the browser's fullscreen mode on and off. It takes two link text options, one for entering fullscreen, and one for exiting it, which are automatically updated to match the browser's current fullscreen state. Note that this will only make the `<html>` element fullscreen, for numerous reasons (mainly that enchanting the `<tw-story>` element with `(enchant:?page,` will deactivate fullscreen if it's the fullscreen element, due to the macro call wrapping `<tw-story>` in another element). Also note that because most browsers allow users to exit fullscreen mode at any time, there's no way to force fullscreen mode into one state or another. Also note that, because browsers only allow fullscreen mode to be entered from interaction elements, like links or buttons, there can't be a low-level "toggle fullscreen" command macro as an alternative to this.
* Added `(checkbox-fullscreen)`, a version of `(link-fullscreen:)` that produces a checkbox instead of a link.
* Added `(link-storylet:)`, a command macro that creates a link to the nth open storylet (or, if a 'where' lambda is given, the first open storylet whose (passage:) datamap matches the lambda).
* Added `(icon-undo:)` and `(icon-redo:)`, two commands that replicate the default sidebar's "undo" and "redo" icons. Use these to reinsert these into the sidebar if you've modified it using `(replace:)` or `(append:)`, or simply place those icons into your passage text if you wish. You can change the icon to use a different Unicode character if you wish. You can also add text labels to these icons, which appear below them.
* Added `(icon-restart:)` and `(icon-fullscreen:)`, two additional commands that can be used, with `(prepend:)` and `(append:)`, to add "fullscreen" and "reload" buttons to the sidebar. The "fullscreen" icon works similarly to the `(link-fullscreen:)` macro described above, while the "reload" icon performs similarly to `(reload:)` when clicked. These offer the same options as `(icon-undo:)` and `(icon-redo:)`.
......
......@@ -514,6 +514,45 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
assign(cd, { verbatim:true, source: printBuiltinValue(val) }),
[Any])
/*d:
(verbatim-source: Any) -> Command
Also known as: (v6m-source:)
A convenient combination of (verbatim-print:) and (source:), this prints out the Harlowe source code of any value given to it.
Example usage:
`(v6m-source: (open-storylets:)'s 1st)` prints the source of the first datamap in the array generated by (open-storylets:).
Rationale:
This macro provides a quick way for you to display the source code of a Harlowe value. Normally, you can't easily print the string
returned by (source:), because, funnily enough, Harlowe will immediately re-render it. You can use this macro instead. This can be
helpful when you're debugging a story and wish to have a complicated expression's value constantly in view, especially in a "debug-footer" tagged passage.
Details:
For more details about the particulars of the source code generated by (source:), see that macro's article. Note that, as with that macro,
commands created by custom macros (via the (output:) macro) cannot be converted to source - attempting to do so will produce an error.
See also:
(verbatim-print:), (source:)
Added in: 3.2.0
#debugging
*/
(["verbatim-source", "v6m-source"],
(val) => {
/*
Note that since custom macro commands cannot be serialised, they can't have a TwineScript_ToSource() method that would return this error
by itself. Also note that every built-in command has a TwineScript_ToSource() installed by Macros.
*/
if (val && val.TwineScript_TypeID === "command" && !val.TwineScript_ToSource) {
return TwineError.create("datatype", "I can't construct the source code of a command created by a custom macro.");
}
},
(cd, _, val) => {
return assign(cd, { verbatim:true, source: printBuiltinValue(toSource(val)) });
},
[Any])
/*d:
(go-to: String) -> Command
This command stops passage code and sends the player to a new passage.
......@@ -799,7 +838,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
Rationale:
By default, each passage in a Harlowe story features a narrow sidebar to the left, housing "Undo" and "Redo" menu icons.
However, other functions may be desirable to have available to the player at all times, such as the capability to toggle fullscreen mode in the browser.
While you could place a (link-fullscreen:) in your passage prose, placing the icon produced by this macro is a slightly more concise solution that fits with
While you could place a (link-fullscreen:) or (checkbox-fullscreen:) in your passage prose, placing the icon produced by this macro is a slightly more concise solution that fits with
the use of the sidebar for view utility commands.
Details:
......@@ -826,7 +865,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
If the browser reports to Harlowe that fullscreen mode is unavailable, then the icon will be hidden, leaving a conspicuous space.
See also:
(link-fullscreen:)
(link-fullscreen:), (checkbox-fullscreen:)
Added in: 3.2.0
#sidebar 4
......@@ -1599,6 +1638,10 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
}
}
));
/*
A unique ID for checkbox elements, to bind their <label>s to them via their name attribute.
*/
let checkboxUUID = 1;
/*d:
(checkbox: Bind, String) -> Command
......@@ -1634,13 +1677,13 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
(dropdown:), (input-box:), (confirm:)
Added in: 3.2.0
#input and interface
#input and interface 5
*/
Macros.addCommand("checkbox",
() => {},
(cd, _, bind, label) => {
let checked = false;
let uniqueName = "checkbox-" + Date.now();
let uniqueName = "checkbox-" + (++checkboxUUID);
/*
If the binding is two-way, add the special [data-2bind] signalling attribute, get the initial checked
value from the bound variable, and set up the two-way binding event.
......@@ -1684,6 +1727,67 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
[VarBind, String]
);
/*d:
(checkbox-fullscreen: String) -> Command
A command that creates a checkbox input, which toggles the browser's fullscreen mode and windowed mode. The checkbox will automatically update to match
the browser's fullscreen status. If fullscreen mode cannot be entered, the checkbox will be disabled.
Example usage:
`(checkbox-fullscreen: "Fullscreen mode")`
Rationale:
Modern browsers allow web pages to take up the entire screen of the user, in a manner similar to desktop games. This feature can be useful
for immersive or moody stories, such as horror stories, that wish to immerse the player's senses in a certain colour or shade, or to display
impactful text that doesn't have to compete for attention from any other screen elements. While it can be more convenient to place an (icon-fullscreen:)
in your story's sidebar, this macro can be useful if you remove or replace the sidebar with something else, and can serve as an alternative
means of activating fullscreen mode.
Details:
When activated, this will make the page's `<html>` element be the fullscreen element, *not* `<tw-story>`. This is because, in most browsers,
removing the fullscreen element from the page, however briefly, will deactivate fullscreen mode. Since the `(enchant:)` macro, when given ?Page,
will often need to wrap `<tw-story>` in another element, those macro calls will deactivate fullscreen mode if `<tw-story>` was the fullscreen element.
So, if you have edited the compiled HTML to add elements before and after it, such as a navigation bar, that will remain visible while the story
is in fullscreen mode. Additionally, this means that the Debug Mode panel is still visible when fullscreen mode is activated.
Currently, there is no special functionality or error reporting when the browser reports that fullscreen mode is available, but the attempt
to switch to fullscreen mode fails. In that case, the link will simply appear to do nothing.
See also:
(checkbox:), (link-fullscreen:), (icon-fullscreen:)
Added in: 3.2.0.
#input and interface 6
*/
Utils.onStartup(() =>
$(document).on('fullscreenchange', () => {
$("input[type=checkbox][id^=fullscreen]", Utils.storyElement).each((_, checkbox) => {
($(checkbox).closest('tw-expression').data('fullscreenEvent') || Object)(checkbox);
});
})
);
Macros.addCommand("checkbox-fullscreen",
() => {},
(cd, _, label) => {
let uniqueName = "fullscreenCheckbox-" + (++checkboxUUID);
cd.data.fullscreenEvent = box => $(box).prop('checked', !!(document.fullscreenElement || document.msFullscreenElement));
cd.data.checkboxEvent = () => Engine.toggleFullscreen();
/*
In HTML, checkboxes and labels are associated with [for] and [id] attribute keys.
*/
return assign(cd, {
source: `<input id="${uniqueName}" type="checkbox" ${
document.fullscreenEnabled || document.msFullscreenEnabled ? ' ' : "disabled "
}${
document.fullscreenElement || document.msFullscreenElement ? 'checked' : ''
}><label for="${uniqueName}">${label}</label>`,
append: "replace",
});
},
[String]
);
/*
This is the shared handler for (input-box:) and (force-input-box:).
*/
......
......@@ -866,8 +866,8 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'engine', 'datatypes/c
Rationale:
Modern browsers allow web pages to take up the entire screen of the user, in a manner similar to desktop games. This feature can be useful
for immersive or moody stories, such as horror stories, that wish to immerse the player's senses in a certain colour or shade, or to display
impactful text that doesn't have to compete for attention from any other screen elements. While a fullscreen button is present in the story's
sidebar by default, this macro can be useful if you remove or replace the sidebar with something else, and can serve as an alternative
impactful text that doesn't have to compete for attention from any other screen elements. While it can be more convenient to place an (icon-fullscreen:)
in your story's sidebar, this macro can be useful if you remove or replace the sidebar with something else, and can serve as an alternative
means of activating fullscreen mode.
The third string is an error message or alternative text you can provide if the browser doesn't allow fullscreen mode to be entered, for whatever reason.
......@@ -896,7 +896,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'engine', 'datatypes/c
this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
See also:
(link-goto:), (link-undo:), (cycling-link:), (icon-fullscreen:)
(link-goto:), (link-undo:), (cycling-link:), (icon-fullscreen:), (checkbox-fullscreen:)
Added in: 3.2.0
#links 8
......
......@@ -152,10 +152,10 @@ define(['macros', 'utils', 'utils/operationutils', 'datatypes/colour', 'datatype
Commands created by custom macros (via the (output:) macro) cannot be given to this macro - attempting to do so will produce an error.
Note that you can't easily print the string returned by (source:), because, funnily enough, Harlowe will immediately
re-render it. You can use (verbatim-print:) to print it without it being rendered.
re-render it. You can use (verbatim-source:) to accomplish this instead.
See also:
(datatype:)
(datatype:), (verbatim-source:)
Added in: 3.2.0
#string
......
......@@ -830,7 +830,7 @@
}
const subrowEl = el(`<label class='harlowe-3-radioRow'><input type="radio" name="macro-list" value="${defs.name}"></input>`
+ `<code><a href="https://twine2.neocities.org/#${defs.anchor}" target="_blank" rel="noopener noreferrer">(${defs.name}: ${defs.sig}) -> ${defs.returnType}</a></code>`
+ `<code><a href="https://twine2.neocities.org/#${defs.anchor}" target="_blank" rel="noopener noreferrer">(${defs.name}: ${defs.sig}) ${defs.returnType}</a></code>`
+ `${defs.aka.length ? `<div><i>Also known as: ${
defs.aka.map(alias => `<code>(${alias}:)</code>`).join(', ')
}</i>` : ''}</div><div>${
......
......@@ -46,7 +46,7 @@
type === "unclosedCollapsed" ? "the remainder of the passage " : 'the <code>{</code> and <code>}</code> marks'
} will be replaced with a single space. You can use this to space out your code and keep your passage readable.<br>To include a line break within this markup that will be preserved, use a HTML <code>&lt;br&gt;</code> tag.`,
escapedLine: `This is an <b>escaped line break</b> mark. This removes the line break before or after it from the displayed passage.`,
twineLink: ({passage}) => `This is a link to the passage "${passage}". Links, like hooks and commands, can have changer values attached to the left.`,
twineLink: ({passage}) => `This is a <b>link to the passage "${passage}"</b>. Links, like hooks and commands, can have changer values attached to the left.`,
br: ``, // Display nothing,
url: ``,
variable: `This is a <b>story-wide variable</b>. After this has been set to a data value, it can be used anywhere else in the story. Use these to store data values related to your story's game state, or changers that are commonly used. ` + variableInProse,
......
......@@ -410,7 +410,8 @@
fn: (match) => ({
type: "twineLink",
innerText: match[1],
passage: match[2]
passage: match[2],
innerMode: markupMode,
}),
},
/*
......@@ -456,6 +457,7 @@
type: "twineLink",
innerText: p2 ? p3 : p1,
passage: p1 ? p3 : p2,
innerMode: markupMode,
};
},
},
......@@ -465,6 +467,7 @@
type: "twineLink",
innerText: match[1] || '',
passage: match[1] || '',
innerMode: markupMode,
}),
},
......@@ -720,7 +723,7 @@
));
/*
TBW
String mode is a special mode which contains only these three elements, designed for the interiors of strings.
*/
const stringRules = setupRules(stringMode, {
singleStringCloser: macroRules.singleStringOpener,
......
......@@ -2,7 +2,9 @@ Introduction 2: Welcome to the world of 3.2.0
Dear fellow author of hypertext fiction: this version of Harlowe is most unlike others before it - despite a commitment to backwards-compatibility letting it be numbered a "minor" release, its wealth of added features is truly incredible. Let me explain in brief how this came to be. In April 2020, as the COVID-19 pandemic death toll (and the even greater permanent disability toll) was steadily rising worldwide, I came to something of a quiet realisation... that it was very possible I would not live to the end of this year, and, furthermore, it was probable that many Harlowe users would not live to the end of this year. I thought about Harlowe, and about how so much functionality I'd always envisioned for it - custom macros, string patterns, fullscreen support - was still unimplemented, and came to a decision that I absolutely had to finally get all of it in, this year.
So, I worked diligently on Harlowe for 9 months. In retrospect, it feels like my life lost all meaning except for Harlowe. But at the end, Harlowe finally became, for the first time, a hypertext fiction coding language I could be proud of. Even now, there's still some extra features I wish I'd been able to fit in, but this version feels like the first version where Harlowe is "complete", as my original vision for the story format in 2014 had resembled.
So, I worked diligently on Harlowe for 9 months. In retrospect, it feels like my life lost all meaning except for Harlowe. Honestly, it was perhaps less like 9 months of productivity and more like a 9-month-long panic attack. (This is what most "COVID-19 productivity" stories are actually like, deep down. Please, don't admire me. Love yourself.)
But at the end, Harlowe finally became, for the first time, a hypertext fiction coding language I could be proud of. Even now, there's still some extra features I wish I'd been able to fit in, but this version feels like the first version where Harlowe is "complete", as my original vision for the story format in 2014 had resembled.
And now you get to enjoy all of this, and more.
......
......@@ -547,6 +547,11 @@ describe("interface macros", function(){
var p = runPassage('(checkbox:bind $foo, "bar")');
expect(p.find('input[type=checkbox]').attr('id')).toBe(p.find('input[type=checkbox] + label').attr('for'));
});
it("consecutive checkboxes have different ids", function() {
var p = runPassage("(checkbox:bind $x,'foo')(checkbox:bind $x,'bar')(checkbox:bind $x,'baz')");
expect(p.find('input').get(0).getAttribute('id')).not.toBe(p.find('input').get(1).getAttribute('id'));
expect(p.find('input').get(1).getAttribute('id')).not.toBe(p.find('input').get(2).getAttribute('id'));
});
it("when clicked, updates the bound variable", function() {
var p = runPassage('(checkbox:bind $foo, "bar")');
p.find('input').click();
......@@ -556,6 +561,10 @@ describe("interface macros", function(){
p.find('input').click();
expect("(print:$foo)").markupToPrint("true");
});
it("renders markup in the label text", function() {
var p = runPassage("(checkbox:bind $x,'//glower//')");
expect(p.find('i').text()).toBe("glower");
});
it("errors if the bound variable can't contain booleans", function() {
var p = runPassage('(set:num-type $foo to 2)(checkbox:bind $foo,"baz")');
p.find('input[type=checkbox]').trigger('input');
......@@ -568,6 +577,35 @@ describe("interface macros", function(){
expect(p.find('input:checked').length).toBe(0);
});
});
describe("(checkbox-fullscreen:)", function() {
it("accepts a string", function() {
expect("(checkbox-fullscreen:)").markupToError();
expect("(checkbox-fullscreen:2)").markupToError();
expect("(checkbox-fullscreen:true)").markupToError();
expect("(checkbox-fullscreen:'')").not.markupToError();
expect("(checkbox-fullscreen:'s')").not.markupToError();
expect("(checkbox-fullscreen:'s','s')").markupToError();
});
it("creates an <input> with a <label> with matching 'for' and 'id' attributes", function() {
var p = runPassage('(checkbox-fullscreen:"bar")');
expect(p.find('input[type=checkbox]').attr('id')).toBe(p.find('input[type=checkbox] + label').attr('for'));
});
it("when clicked, toggles fullscreen mode on <html>", function() {
spyOn(document.documentElement,'requestFullscreen');
runPassage("(checkbox-fullscreen:'mire')").find('input').click();
expect(document.documentElement.requestFullscreen).toHaveBeenCalled();
});
it("consecutive checkboxes have different ids", function() {
var p = runPassage("(checkbox-fullscreen:'foo')(checkbox-fullscreen:'bar')(checkbox-fullscreen:'baz')");
expect(p.find('input').get(0).getAttribute('id')).not.toBe(p.find('input').get(1).getAttribute('id'));
expect(p.find('input').get(1).getAttribute('id')).not.toBe(p.find('input').get(2).getAttribute('id'));
});
it("renders markup in the label text", function() {
var p = runPassage("(checkbox-fullscreen:'//glower//')");
expect(p.find('i').text()).toBe("glower");
});
});
describe("the (meter:) macro", function() {
it("accepts a bound number variable, a positive number, a sizing string, an optional string label, and an optional colour or gradient", function() {
expect("(meter:)").markupToError();
......
......@@ -406,75 +406,84 @@ describe("data structure macros", function () {
expect("(count: (a:(a:2,3),(a:2,3)), (a:2,3))").markupToPrint('2');
});
});
describe("the (source:) macro", function() {
function sourceTest(val,result) {
expect("(print:'`'+(source:"+val+")+'`')").markupToPrint(result);
}
it("serialises basic types", function() {
sourceTest("1.56+1","2.56");
sourceTest("21s","21000");
sourceTest("'fo' + 'obar'","\"foobar\"");
sourceTest("not true","false");
});
it("serialises basic data structures, sorted if the structure isn't sequential", function() {
sourceTest("(a:2,(a:5))","(a:2,(a:5))");
sourceTest("(sorted:5,1,2,3,4)","(a:1,2,3,4,5)");
sourceTest("(a:(a:...(a:2,3,4)))'s 1st","(a:2,3,4)");
['source','verbatim-source'].forEach(function(e,i) {
describe("the (source:) macro", function() {
function sourceTest(val,result) {
expect(i ? ("(verbatim-source:"+val+")") : ("(print:'`'+(source:"+val+")+'`')")).markupToPrint(result);
}
it("serialises basic types", function() {
sourceTest("1.56+1","2.56");
sourceTest("21s","21000");
sourceTest("'fo' + 'obar'","\"foobar\"");
sourceTest("not true","false");
});
it("serialises basic data structures, sorted if the structure isn't sequential", function() {
sourceTest("(a:2,(a:5))","(a:2,(a:5))");
sourceTest("(sorted:5,1,2,3,4)","(a:1,2,3,4,5)");
sourceTest("(a:(a:...(a:2,3,4)))'s 1st","(a:2,3,4)");
sourceTest("(dm:'foo',1,'bar',2)",'(dm:"bar",2,"foo",1)');
sourceTest("(dm:)","(dm:)");
sourceTest("(dm:'foo',1,'bar',2)",'(dm:"bar",2,"foo",1)');
sourceTest("(dm:)","(dm:)");
sourceTest("(ds:5,1,2,3,4)","(ds:1,2,3,4,5)");
sourceTest("(ds:)","(ds:)");
});
it("serialises changers and combined changers", function() {
['(link:"foo")','(hidden:)','(text-indent:2)','(transition:"instant")','(if:true)',
'(text-indent:2)+(css:"display:block;")','(transition:"instant")+(transition-delay:20)',
'(replace:?foo\'s 1st)','(append:"bar")',"(prepend:?foo's last + ?bar's 3rdlast + ?baz's (a:2,5))"
].forEach(function(e){ sourceTest(e,e); });
sourceTest("(size:10)",'(text-size:10)');
});
it("serialises changers and commands with optional arguments", function() {
expect("(set: $a to (source: (seq-link:bind $foo,'A','B')))").not.markupToError();
expect("(set: $a to (source: (seq-link:'A','B')))").not.markupToError();
expect("(set: $a to (source: (box:'A')))").not.markupToError();
expect("(set: $a to (source: (box:'A',1)))").not.markupToError();
expect("(set: $a to (source: (linkgoto:'A')))").not.markupToError();
expect("(set: $a to (source: (linkgoto:'A','B')))").not.markupToError();
});
it("serialises colours", function() {
sourceTest("#ff0009",'(hsl:358,1,0.5)');
sourceTest("(hsl:56,1,0.5,0.3)",'(hsl:56,1,0.5,0.3)');
sourceTest("(lch:0.6,80,122)",'(lch:0.6,80,122)');
sourceTest("black","black");
sourceTest("white","white");
});
it("serialises gradients", function() {
sourceTest("(gradient: 0, 0, black, 0.49, #ff0009, 0.5, white, 1, white)",'(gradient:0,0,black,0.49,(hsl:358,1,0.5),0.5,white,1,white)');
});
it("serialises commands", function() {
['(cycling-link:bind $foo,"bar","baz")','(click-goto:"qux","test")',
'(enchant:?foo,(transition:"instant")+(transition-delay:20))',
'(show:(hooks-named:"foo baz"))',
].forEach(function(e){ sourceTest(e,e); });
runPassage("(set:$baz to 4)");
sourceTest("(print:$baz)",'(print:4)');
sourceTest('(sequence-link:2bind $foo,"bar","baz")','(seq-link:2bind $foo,"bar","baz")');
});
it("serialises custom macros", function() {
sourceTest('(macro:boolean-type _ok,num-type _ng,[(output:(cond:_ok,_ng,1)])','(macro:bool-type _ok,num-type _ng,[(output:(cond:_ok,_ng,1)])');
sourceTest('(macro:[])','(macro:[])');
});
it("serialises lambdas", function() {
[
'_item making _total via _total + (max: _item, 0)', '_item where _item\'s 1st is "A"', 'when $fuel > 8', '_item via _item + "s"', 'each _item'
].forEach(function(e){ sourceTest(e,e); });
});
it("doesn't serialise errors", function() {
expect('(source:"red"+2)').markupToError();
});
it("doesn't serialise custom macros' commands", function() {
expect('(set:$a to (macro:[(output:)[]]))(source:($a:))').markupToError();
sourceTest("(ds:5,1,2,3,4)","(ds:1,2,3,4,5)");
sourceTest("(ds:)","(ds:)");
});
it("serialises changers and combined changers", function() {
['(link:"foo")','(hidden:)','(text-indent:2)','(transition:"instant")','(if:true)',
'(text-indent:2)+(css:"display:block;")','(transition:"instant")+(transition-delay:20)',
'(replace:?foo\'s 1st)','(append:"bar")',"(prepend:?foo's last + ?bar's 3rdlast + ?baz's (a:2,5))"
].forEach(function(e){ sourceTest(e,e); });
sourceTest("(size:10)",'(text-size:10)');
});
it("serialises colours", function() {
sourceTest("#ff0009",'(hsl:358,1,0.5)');
sourceTest("(hsl:56,1,0.5,0.3)",'(hsl:56,1,0.5,0.3)');
sourceTest("(lch:0.6,80,122)",'(lch:0.6,80,122)');
sourceTest("black","black");
sourceTest("white","white");
});
it("serialises gradients", function() {
sourceTest("(gradient: 0, 0, black, 0.49, #ff0009, 0.5, white, 1, white)",'(gradient:0,0,black,0.49,(hsl:358,1,0.5),0.5,white,1,white)');
});
it("serialises commands", function() {
['(cycling-link:bind $foo,"bar","baz")','(click-goto:"qux","test")',
'(enchant:?foo,(transition:"instant")+(transition-delay:20))',
'(show:(hooks-named:"foo baz"))',
].forEach(function(e){ sourceTest(e,e); });
runPassage("(set:$baz to 4)");
sourceTest("(print:$baz)",'(print:4)');
sourceTest('(sequence-link:2bind $foo,"bar","baz")','(seq-link:2bind $foo,"bar","baz")');
});
it("serialises custom macros", function() {
sourceTest('(macro:boolean-type _ok,num-type _ng,[(output:(cond:_ok,_ng,1)])','(macro:bool-type _ok,num-type _ng,[(output:(cond:_ok,_ng,1)])');
sourceTest('(macro:[])','(macro:[])');
});
it("serialises lambdas", function() {
[
'_item making _total via _total + (max: _item, 0)', '_item where _item\'s 1st is "A"', 'when $fuel > 8', '_item via _item + "s"', 'each _item'
].forEach(function(e){ sourceTest(e,e); });
});
it("doesn't serialise errors", function() {
expect('(' + e + ':"red"+2)').markupToError();
});
if (i === 0) {
it("serialises changers and commands with optional arguments", function() {
expect("(set: $a to (" + e + ": (seq-link:bind $foo,'A','B')))").not.markupToError();
expect("(set: $a to (" + e + ": (seq-link:'A','B')))").not.markupToError();
expect("(set: $a to (" + e + ": (box:'A')))").not.markupToError();
expect("(set: $a to (" + e + ": (box:'A',1)))").not.markupToError();
expect("(set: $a to (" + e + ": (linkgoto:'A')))").not.markupToError();
expect("(set: $a to (" + e + ": (linkgoto:'A','B')))").not.markupToError();
});
}
else {
it("is aliased as (v6m-source:)", function() {
expect("(print:(v6m-source:2) is (verbatim-source:2))").markupToPrint('true');
});
}
it("doesn't serialise custom macros' commands", function() {
expect('(set:$a to (macro:[(output:)[]]))(' + e + ':($a:))').markupToError();
});
});
});
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment