aw-parser
Version:
Fountain parser
304 lines (237 loc) • 11.5 kB
JavaScript
var parser = require('../parser');
var testHelper = require('./helper/test-helper');
var chai = require('chai');
describe('Parser', function() {
var script, config, result;
it('Returns an empty result for an empty script', function() {
result = parser.parse('', testHelper.getConfigWith(true));
chai.assert.lengthOf(result.title_page, 0);
chai.assert.lengthOf(result.tokens, 0);
});
it('Parsing types', function() {
config = testHelper.getConfigWith(true);
script = '#Section\n\n=Synopsis\n\nINT. HEADER 1\n\nAction\n\nHERO\n(Parenthetical)\nDialogue';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, [
'section', 'separator',
'synopsis', 'separator',
'scene_heading', 'separator',
'action', 'separator',
'character',
'parenthetical',
'dialogue'
]);
});
it('Parses inline capitals as action', function() {
config = testHelper.getConfigWith(true);
script = "Action\nACTION.\nAction";
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['action', 'action', 'action']);
});
it('Parses shot', function() {
config = testHelper.getConfigWith(true);
script = "Action\n\nSHOT\n\nAction";
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['action', 'separator', 'shot', 'separator', 'action']);
});
it('Ignores blank lines before first token', function() {
script = '\n\n\n\n\nTitle:Title';
result = parser.parse(script, config);
chai.assert.strictEqual(result.title_page[0].text, 'Title');
});
describe('Formatting', function() {
it('Page break', function() {
script = 'Action\n\n===\n\nAction';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['action', 'separator', 'page_break', 'separator', 'action']);
});
it('Centered', function() {
script = '> centered <';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['centered']);
});
it('Dual dialogue', function() {
script = 'HERO\nHello!\n\nHERO 2 ^\nHello!\n';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['character', 'dialogue', 'separator', 'character', 'dialogue']);
chai.assert.strictEqual(result.tokens[0].dual, 'left');
chai.assert.strictEqual(result.tokens[1].dual, 'left');
chai.assert.strictEqual(result.tokens[3].text, 'HERO 2');
chai.assert.strictEqual(result.tokens[3].dual, 'right');
chai.assert.strictEqual(result.tokens[4].dual, 'right');
});
it('"^" when dual dialogue is disabled', function() {
config.use_dual_dialogue = false;
script = 'HERO\nHello!\n\nHERO 2 ^\nHello!\n';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['character', 'dialogue', 'separator', 'character', 'dialogue']);
chai.assert.strictEqual(result.tokens[3].text, 'HERO 2');
});
it('Scene numbers', function() {
script = 'INT. SCENE #2#';
result = parser.parse(script, config);
chai.assert.strictEqual(result.tokens[1].number, '2');
chai.assert.strictEqual(result.tokens[1].text, 'INT. SCENE');
chai.assert.strictEqual(result.tokens[1].type, 'scene_heading');
});
});
describe('Forced elements', function() {
beforeEach(function() {
config = testHelper.getConfigWith(true);
});
it('Non-breaking line in title tag', function() {
script = 'Title:Line 1\n \nLine 2';
result = parser.parse(script, config);
chai.assert.strictEqual(result.title_page[0].text, 'Line 1\n\nLine 2');
});
it('Non-breaking line in script tag', function() {
script = 'HERO\nDialogue\n \n...still in dialogue';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['character', 'dialogue', 'dialogue', 'dialogue']);
chai.assert.strictEqual(result.tokens[1].text, 'Dialogue');
chai.assert.strictEqual(result.tokens[2].text, '');
chai.assert.strictEqual(result.tokens[3].text, '...still in dialogue');
});
it('Forced scene heading', function() {
script = '.HEADING';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['scene_heading']);
});
it('Forced dialogue', function() {
script = '@HERO\nDialogue';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['character', 'dialogue']);
});
it('Forced action', function() {
script = '!INT. ACTION';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['action']);
});
it('Transition', function() {
script = '>Cut to';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['transition']);
});
it('Forced heading takes priority over default transitions', function() {
script = '.CUT TO:';
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['scene_heading']);
});
});
describe('Retain whitespace', function() {
beforeEach(function() {
config = testHelper.getConfigWith(true);
});
it('Retains whitespace on action elements', function() {
var lines = [
'Action line.',
'\tAction line with tabs\t',
' Action line with spaces '
];
script = lines.join('\n');
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['action', 'action', 'action']);
chai.assert.strictEqual(result.tokens[0].text, lines[0]);
chai.assert.strictEqual(result.tokens[1].text, lines[1]);
chai.assert.strictEqual(result.tokens[2].text, lines[2]);
});
it('Does not retain whitespace on non-action elements', function() {
var lines = [
'HERO',
'Dialogue line.',
'\tDialogue line with tabs\t',
' Dialogue line with spaces '
];
script = lines.join('\n');
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['character', 'dialogue', 'dialogue', 'dialogue']);
chai.assert.strictEqual(result.tokens[0].text, lines[0].trim());
chai.assert.strictEqual(result.tokens[1].text, lines[1].trim());
chai.assert.strictEqual(result.tokens[2].text, lines[2].trim());
chai.assert.strictEqual(result.tokens[3].text, lines[3].trim());
});
});
describe('Newline', function() {
var crlf, cr, lf, config;
beforeEach(function() {
config = testHelper.getConfigWith(true);
config.double_space_between_scenes = false;
config.each_scene_on_new_page = false;
crlf = 'INT. TEXT\r\n\r\nAction\r\n\r\nINT. TEXT\r\n\r\nAction';
lf = 'INT. TEXT\r\rAction\r\rINT. TEXT\r\rAction';
cr = 'INT. TEXT\n\nAction\n\nINT. TEXT\n\nAction';
});
it('Supports different new line types', function() {
var crlfTokens = parser.parse(crlf, config).tokens,
lfTokens = parser.parse(lf, config).tokens,
crTokens = parser.parse(cr, config).tokens,
tokens = ['scene_heading', 'separator', 'action', 'separator', 'scene_heading', 'separator', 'action'];
testHelper.verifyTokenTypes(crlfTokens, tokens);
testHelper.verifyTokenTypes(crTokens, tokens);
testHelper.verifyTokenTypes(lfTokens, tokens);
});
});
describe('Config', function() {
beforeEach(function() {
config = testHelper.getConfigWith(false);
});
it('Breaking a page before a header', function() {
script = 'INT. HEADER 1\n\nAction\n\nINT. HEADER 2';
config.print_headers = true;
config.print_actions = true;
config.each_scene_on_new_page = true;
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['scene_heading', 'separator', 'action', 'separator', 'page_break', 'scene_heading']);
});
it('Double space before headers', function() {
script = 'INT. HEADER 1\n\nAction\n\nINT. HEADER 2';
config.print_headers = true;
config.print_actions = true;
config.double_space_between_scenes = true;
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['scene_heading', 'separator', 'action', 'separator', 'separator', 'scene_heading']);
});
it('Ignoring types', function() {
script = '#Section\n\n=Synopsis\n\nINT. HEADER 1\n\nAction\n\nHERO\nDialogue\n\nINT. HEADER 2';
result = parser.parse(script, config);
chai.assert.lengthOf(result.tokens, 0);
});
it('Skipping notes', function() {
script = 'Test [[inline note]] test.';
config.print_actions = true;
result = parser.parse(script, config);
chai.assert.strictEqual(result.tokens[0].text, 'Test test.');
});
it('Multiple empty lines', function() {
script = 'INT. HEADER 1\n\nAction\n\n\n\nAction';
config.print_headers = true;
config.print_actions = true;
config.merge_multiple_empty_lines = false;
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, ['scene_heading', 'separator', 'action', 'separator', 'separator', 'separator', 'action']);
});
it('Multiple empty lines with double space between headers', function() {
script = 'INT. HEADER\n\nAction.\n\nINT. HEADER\n\nAction.\n\n\nINT. HEADER\n\nAction.';
config.print_headers = true;
config.print_actions = true;
config.merge_multiple_empty_lines = false;
config.double_space_between_scenes = true;
result = parser.parse(script, config);
testHelper.verifyTokenTypes(result.tokens, [
'scene_heading',
'separator',
'action',
'separator',
'separator', // separator added automatically
'scene_heading',
'separator',
'action',
'separator',
'separator',
'separator', // extra separator added anyway, even if two lines are already added
'scene_heading',
'separator',
'action']);
});
});
});