Skip to content

Commit

Permalink
fix: Remove whitespace before a command in dialogue option (#2187)
Browse files Browse the repository at this point in the history
Previously, a line such as -> Choice 1 <<if $condition>> would generate text "Choice 1 " (with an extra space at the end), this is now fixed.

Also added several tests from the Yarn integration testing suite.
  • Loading branch information
st-pasha authored Nov 24, 2022
1 parent 9e677e7 commit 00f0e33
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 32 deletions.
12 changes: 7 additions & 5 deletions packages/flame_jenny/jenny/lib/src/parse/tokenize.dart
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,9 @@ class _Lexer {
cu == $lessThan ||
cu == $greaterThan ||
cu == $leftBrace ||
cu == $rightBrace) {
cu == $rightBrace ||
cu == $leftBracket ||
cu == $rightBracket) {
pushToken(Token.text(String.fromCharCode(cu)), position);
position += 1;
} else if (cu == $lowercaseN) {
Expand Down Expand Up @@ -684,17 +686,17 @@ class _Lexer {
var positionBeforeWhitespace = position;
while (!eof) {
final cu = currentCodeUnit;
// Stop when seeing (\n|\r|#|//) and discard any preceding whitespace
// Stop when seeing (\n|\r|#|//|<<) and discard any preceding whitespace
if ((cu == $slash && nextCodeUnit == $slash) ||
(cu == $lessThan && nextCodeUnit == $lessThan) ||
cu == $hash ||
cu == $carriageReturn ||
cu == $lineFeed) {
position = positionBeforeWhitespace;
break;
}
// Stop when seeing (<<|>>|\\|{|[) and keep the whitespace
else if ((cu == $lessThan && nextCodeUnit == $lessThan) ||
(cu == $greaterThan && nextCodeUnit == $greaterThan) ||
// Stop when seeing (>>|\\|{|[) and keep the whitespace
else if ((cu == $greaterThan && nextCodeUnit == $greaterThan) ||
cu == $backslash ||
cu == $leftBrace ||
cu == $leftBracket) {
Expand Down
7 changes: 3 additions & 4 deletions packages/flame_jenny/jenny/test/dialogue_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ void main() {
'[*] onDialogueStart()',
'[*] onNodeStart(Node(X))',
'[*] onChoiceStart(DialogueChoice([Option(Hi there), ' +
'Option(Howdy), Option(Yo! #disabled)])) -> 1',
'Option(Howdy), Option(Yo! #disabled)])) -> 1',
'[*] onChoiceFinish(Option(Howdy))',
'[*] onLineStart(DialogueLine(Greetings to you too))',
'[*] onLineFinish(DialogueLine(Greetings to you too))',
Expand All @@ -154,7 +154,7 @@ void main() {
'[*] onDialogueStart()',
'[*] onNodeStart(Node(X))',
'[*] onChoiceStart(DialogueChoice([Option(Hi there), ' +
'Option(Howdy), Option(Yo! #disabled)])) -> 0',
'Option(Howdy), Option(Yo! #disabled)])) -> 0',
'[*] onChoiceFinish(Option(Hi there))',
'[*] onLineStart(DialogueLine(Kk-thx-bye))',
'[*] onLineFinish(DialogueLine(Kk-thx-bye))',
Expand All @@ -179,7 +179,7 @@ void main() {
() => dialogue.runNode('A'),
hasDialogueError(
'A dialogue view selected a disabled option: '
'Option(Only two #disabled)',
'Option(Only two #disabled)',
),
);
});
Expand Down Expand Up @@ -306,7 +306,6 @@ void main() {
line: All done with the shortcut options!
''',
commands: ['this'],
skip: true,
);
});
}
Expand Down
164 changes: 147 additions & 17 deletions packages/flame_jenny/jenny/test/parse/parse_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:jenny/src/structure/commands/jump_command.dart';
import 'package:jenny/src/structure/dialogue_entry.dart';
import 'package:test/test.dart';

import '../test_scenario.dart';
import '../utils.dart';

void main() {
Expand Down Expand Up @@ -113,6 +114,60 @@ void main() {
),
);
});

test('Headers.yarn', () {
final yarn = YarnProject();
yarn.parse(
dedent('''
title: EmptyTags
tags:
---
In this test, the 'tags' header is provided, but has no value.
===
title: Tags
tags: one two three
---
In this test, the 'tags' header is provided, and has three values.
===
title: ArbitraryHeaderWithValue
// test
arbitraryHeader: some-arbitrary-text
---
In this test, an arbitrary header is defined with some text.
===
title: Comments
tags: one two three
metadata:
---
This node demonstrates the use of comments in node headers.
===
'''),
);
final node1 = yarn.nodes['EmptyTags']!;
expect(node1.tags, isNotNull);
expect(node1.tags!['tags'], '');

final node2 = yarn.nodes['Tags']!;
expect(node2.tags!['tags'], 'one two three');

final node3 = yarn.nodes['ArbitraryHeaderWithValue']!;
expect(node3.tags!['arbitraryHeader'], 'some-arbitrary-text');

final node4 = yarn.nodes['Comments']!;
expect(node4.tags!['tags'], 'one two three');
expect(node4.tags!['metadata'], '');
});

test(
'InvalidNodeTitle.yarn',
() {
expect(
() => YarnProject().parse('title: \$InvalidTitle\n---\n===\n'),
hasNameError(r'NameError: invalid title name "$InvalidTitle"'),
);
},
skip: true,
);
});

group('parseNodeBody', () {
Expand Down Expand Up @@ -693,23 +748,20 @@ void main() {
);
});

test(
'<<jump>>',
() {
final yarn = YarnProject()
..setVariable(r'$target', 'DOWN')
..parse('title:A\n---\n'
'<<jump UP>>\n'
'<<jump {\$target}>>\n'
'===\n');
final node = yarn.nodes['A']!;
expect(node.lines.length, 2);
expect(node.lines[0], isA<JumpCommand>());
expect(node.lines[1], isA<JumpCommand>());
expect((node.lines[0] as JumpCommand).target.value, 'UP');
expect((node.lines[1] as JumpCommand).target.value, 'DOWN');
},
);
test('<<jump>>', () {
final yarn = YarnProject()
..setVariable(r'$target', 'DOWN')
..parse('title:A\n---\n'
'<<jump UP>>\n'
'<<jump {\$target}>>\n'
'===\n');
final node = yarn.nodes['A']!;
expect(node.lines.length, 2);
expect(node.lines[0], isA<JumpCommand>());
expect(node.lines[1], isA<JumpCommand>());
expect((node.lines[0] as JumpCommand).target.value, 'UP');
expect((node.lines[1] as JumpCommand).target.value, 'DOWN');
});
});

group('parseMarkupTag', () {
Expand Down Expand Up @@ -1079,5 +1131,83 @@ void main() {
});
});
});

testScenario(
testName: 'Escaping.yarn',
input: r'''
title: Start
---
Here's a line with a hashtag #hashtag
Here's a line with an escaped hashtag \#hashtag
Here's a line with an expression {0}
Here's a line with an escaped expression \{0\}
// Commented out because this isn't actually allowed, but we're just
// maintaining the pattern here:
// Here's a line with a command <<foo>
Here's a line with an escaped command \<\<foo\>\>
Here's a line with a comment // wow
Here's a line with an escaped comment \/\/ wow
Here's a line with an escaped backslash \\
Here's some styling with a color code: <color=\#fff>wow</color>
Here's a url: http:\/\/github.com\/YarnSpinnerTool
// Escaped markup is handled by the LineParser class, not the main
// grammar itself
Here's some markup: [a]hello[/a]
Here's some escaped markup: \[a\]hello\[/a\]
-> Here's an option with a hashtag #hashtag
-> Here's an option with an escaped hashtag \#hashtag
-> Here's an option with an expression {0}
-> Here's an option with an escaped expression \{0\}
// Commented out because this isn't actually allowed, but we're just
// maintaining the pattern here:
-> Here's an option with a condition <<if true>>
-> Here's an option with an escaped condition \<\<if true\>\>
-> Here's an option with a comment // wow
-> Here's an option with an escaped comment \/\/ wow
-> Here's an option with an escaped backslash \\
-> Here's some styling with a color code: <color=\#fff>wow</color>
-> Here's a url: http:\/\/github.com\/YarnSpinnerTool
// Escaped markup is handled by the LineParser class, not the main
// grammar itself
-> Here's some markup: [a]hello[/a]
-> Here's some escaped markup: \[a\]hello\[/a\]
===
''',
testPlan: r'''
line: Here's a line with a hashtag
line: Here's a line with an escaped hashtag #hashtag
line: Here's a line with an expression 0
line: Here's a line with an escaped expression {0}
line: Here's a line with an escaped command <<foo>>
line: Here's a line with a comment
line: Here's a line with an escaped comment // wow
line: Here's a line with an escaped backslash \
line: Here's some styling with a color code: <color=#fff>wow</color>
line: Here's a url: http://github.com/YarnSpinnerTool
line: Here's some markup: hello
line: Here's some escaped markup: [a]hello[/a]
option: Here's an option with a hashtag
option: Here's an option with an escaped hashtag #hashtag
option: Here's an option with an expression 0
option: Here's an option with an escaped expression {0}
option: Here's an option with a condition
option: Here's an option with an escaped condition <<if true>>
option: Here's an option with a comment
option: Here's an option with an escaped comment // wow
option: Here's an option with an escaped backslash \
option: Here's some styling with a color code: <color=#fff>wow</color>
option: Here's a url: http://github.com/YarnSpinnerTool
option: Here's some markup: hello
option: Here's some escaped markup: [a]hello[/a]
select: 1
''',
);
});
}
2 changes: 1 addition & 1 deletion packages/flame_jenny/jenny/test/parse/tokenize_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ void main() {
Token.endHeader,
Token.startBody,
Token.arrow,
Token.text('Sure I am! The boss knows me! '),
Token.text('Sure I am! The boss knows me!'),
Token.startCommand,
Token.commandIf,
Token.startExpression,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:test/test.dart';

import '../../test_scenario.dart';

void main() {
group('arithmetic operations', () {
testScenario(
testName: 'Expressions.yarn',
input: r'''
<<declare $int = 0>>
<<declare $bool = true>>
<<declare $math = 0>>
---
title: Start
---
// Expression testing
<<set $int to 1>>
1. {$int == 1}
2. {$int != 2}
// Test unary operators
3. {!$bool == false}
4. {-$int == -1}
5. {-$int == 0 - 1}
// Test more complex expressions
<<set $math = 5 * 2 - 2 * -1 >>
6. {$math is 12}
// Test % operator
<<set $math = 12 >>
<<set $math = $math % 5 >>
7. {$math is 2}
// Test floating point math
8. {1 / 2 == 0.5}
9. {0.1 + 0.1 == 0.2}
===
''',
testPlan: '''
line: 1. true
line: 2. true
line: 3. true
line: 4. true
line: 5. true
line: 6. true
line: 7. true
line: 8. true
line: 9. true
''',
);
});
}
12 changes: 7 additions & 5 deletions packages/flame_jenny/jenny/test/test_scenario.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Future<void> testScenario({
() async {
final yarn = YarnProject();
commands?.forEach(yarn.commands.addDialogueCommand);
yarn.parse(_dedent(input));
final plan = _TestPlan(_dedent(testPlan));
yarn.parse(dedent(input));
final plan = _TestPlan(dedent(testPlan));
final dialogue = DialogueRunner(yarnProject: yarn, dialogueViews: [plan]);
await dialogue.runNode(plan.startNode);
assert(
Expand All @@ -39,7 +39,7 @@ Future<void> testScenario({
}

/// Removes common indent from a multi-line [input] string.
String _dedent(String input) {
String dedent(String input) {
var commonIndent = 1000;
final lines = const LineSplitter().convert(input);
for (final line in lines) {
Expand Down Expand Up @@ -142,11 +142,13 @@ class _TestPlan extends DialogueView {
(option2.available ? '' : ' [disabled]');
assert(
text1 == text2,
'Expected (${i + 1}): $option1\n'
'Actual (${i + 1}): $option2\n',
'\n'
'Expected (${i + 1}): $text1\n'
'Actual (${i + 1}): $text2\n',
);
assert(
option1.enabled == option2.available,
'\n'
'Expected option(${i + 1}): $option1; available=${option1.enabled}\n'
'Actual option(${i + 1}): $option2; available=${option2.available}\n',
);
Expand Down

0 comments on commit 00f0e33

Please sign in to comment.