const { StreamParser } = require( '@codemirror/language' );
const { Tag } = require( '@lezer/highlight' );
const CodeMirror = require( '../../resources/codemirror.js' );
const mediaWikiLang = require( '../../resources/codemirror.mediawiki.js' );
const mwModeConfig = require( '../../resources/codemirror.mediawiki.config.js' );
// NOTE: each test case should have a space before the closing
// This is to avoid interactive UI components from showing up in the test output.
const testCases = [
{
title: 'p tags, extra closing tag',
input: 'this is
content',
output: '
this is <p><div>content</p></p>
'
},
{
title: 'HTML tag attributes',
input: '
',
output: '
<span title="a<b"><b title="a>b"></b></span>
'
},
{
title: 'ref tag attributes',
input: '
',
output: '
<ref name="a<b"/>
'
},
{
title: 'indented table with caption and inline headings',
input: ':{|\n|}\n :: {| class="wikitable"\n |+ Caption\n |-\n ! Uno !! Dos\n |-\n | Foo || Bar\n |}',
output: '
:{|
|}
:: {| class="wikitable"
|+ Caption
|-
! Uno !! Dos
|-
| Foo || Bar
|}
'
},
{
title: 'apostrophe before italic',
input: 'plain l\'\'\'italic\'\'plain',
output: '
plain l\'\'\'italic\'\'plain
'
},
{
title: 'free external links',
input: '//archive.org [ftp://foo.bar FOO] https://wikimedia.org/~\nx',
output: '
//archive.org [ftp://foo.bar FOO] https://wikimedia.org/~
x
'
},
{
title: 'not free external links',
input: 'news: foo news:bar [news: baz]',
output: '
news: foo news:bar [news: baz]
'
},
{
title: 'void tags',
input: 'a
bc a
b
c
d',
output: '
a<br>b</br>c a<div>b<br>c</div>d
'
},
{
title: 'magic words',
input: '__NOTOC__',
output: '
__NOTOC__
'
},
{
title: 'nowiki',
input: '
{{foo}}
{{{\n<pre-foobar><p? ><b!-- '
},
{
title: 'Localized parser function',
input: '{{מיון רגיל:AAA}}',
output: '
{{מיון רגיל:AAA}}
'
}
];
// Setup CodeMirror instance.
const textarea = document.createElement( 'textarea' );
document.body.appendChild( textarea );
const cm = new CodeMirror( textarea );
// Stub the config normally provided by mw.config.get('extCodeMirrorConfig')
const mwLang = mediaWikiLang( {}, {
urlProtocols: 'ftp://|https://|news:',
doubleUnderscore: [ {
__notoc__: 'notoc'
} ],
functionSynonyms: [ {}, {
'!': '!',
'מיון רגיל': 'defaultsort'
} ],
tags: {
nowiki: true,
pre: true,
ref: true,
references: true,
// Made-up tag, for testing when a corresponding TagMode is not configured.
myextension: true
},
tagModes: {
ref: 'mediawiki',
references: 'mediawiki'
}
} );
cm.initialize( [ ...cm.defaultExtensions, mwLang ] );
describe( 'CodeMirrorModeMediaWiki', () => {
it.each( testCases )(
'syntax highlighting ($title)',
( { input, output } ) => {
cm.view.dispatch( {
changes: {
from: 0,
to: cm.view.state.doc.length,
insert: input + ' '
},
// Above we add an extra space to the end, and here we've move the cursor there.
// This is to avoid bracket matching and other interactive UI components
// from showing up in the test output.
selection: { anchor: input.length + 1 }
} );
cm.$textarea.textSelection = jest.fn().mockReturnValue( input );
expect( cm.view.dom.querySelector( '.cm-content' ).innerHTML ).toStrictEqual( output );
}
);
it( 'configuration contains all expected tokens', () => {
expect( Object.keys( mwModeConfig.tags ) ).toStrictEqual( [
'apostrophes',
'apostrophesBold',
'apostrophesItalic',
'comment',
'doubleUnderscore',
'extLink',
'extLinkBracket',
'extLinkProtocol',
'extLinkText',
'hr',
'htmlTagAttribute',
'htmlTagBracket',
'htmlTagName',
'indenting',
'linkBracket',
'linkDelimiter',
'linkText',
'linkToSection',
'list',
'parserFunction',
'parserFunctionBracket',
'parserFunctionDelimiter',
'parserFunctionName',
'sectionHeader',
'sectionHeader1',
'sectionHeader2',
'sectionHeader3',
'sectionHeader4',
'sectionHeader5',
'sectionHeader6',
'signature',
'tableBracket',
'tableDefinition',
'tableDelimiter',
'template',
'templateArgumentName',
'templateBracket',
'templateDelimiter',
'templateName',
'templateVariable',
'templateVariableBracket',
'templateVariableName',
// Custom tags
'em',
'error',
'extNowiki',
'extPre',
'extTag',
'extTagAttribute',
'extTagBracket',
'extTagName',
'freeExtLink',
'freeExtLinkProtocol',
'htmlEntity',
'link',
'linkPageName',
'nowiki',
'pageName',
'pre',
'section',
'skipFormatting',
'strong',
'tableCaption',
'templateVariableDelimiter'
] );
} );
it( 'configuration has a TagStyle for all expected CSS classes', () => {
/** @type {StreamParser} */
const mockContext = {
tokenTable: jest.fn().mockReturnValue( Tag.define() )
};
const cssClasses = mwModeConfig.getTagStyles( mockContext )
.map( ( tagStyle ) => tagStyle.class );
expect( cssClasses ).toStrictEqual( [
'cm-mw-apostrophes',
'cm-mw-apostrophes-bold',
'cm-mw-apostrophes-italic',
'cm-mw-comment',
'cm-mw-double-underscore',
'cm-mw-extlink',
'cm-mw-extlink-bracket',
'cm-mw-extlink-protocol',
'cm-mw-extlink-text',
'cm-mw-hr',
'cm-mw-htmltag-attribute',
'cm-mw-htmltag-bracket',
'cm-mw-htmltag-name',
'cm-mw-indenting',
'cm-mw-link-bracket',
'cm-mw-link-delimiter',
'cm-mw-link-text',
'cm-mw-link-tosection',
'cm-mw-list',
'cm-mw-parserfunction',
'cm-mw-parserfunction-bracket',
'cm-mw-parserfunction-delimiter',
'cm-mw-parserfunction-name',
'cm-mw-section-header',
'cm-mw-section-1',
'cm-mw-section-2',
'cm-mw-section-3',
'cm-mw-section-4',
'cm-mw-section-5',
'cm-mw-section-6',
'cm-mw-signature',
'cm-mw-table-bracket',
'cm-mw-table-definition',
'cm-mw-table-delimiter',
'cm-mw-template',
'cm-mw-template-argument-name',
'cm-mw-template-bracket',
'cm-mw-template-delimiter',
'cm-mw-pagename cm-mw-template-name',
'cm-mw-templatevariable',
'cm-mw-templatevariable-bracket',
'cm-mw-templatevariable-name',
// Custom tags
'cm-mw-em',
'cm-mw-error',
'cm-mw-ext-nowiki',
'cm-mw-ext-pre',
'cm-mw-exttag-bracket',
'cm-mw-exttag',
'cm-mw-exttag-attribute',
'cm-mw-exttag-name',
'cm-mw-free-extlink',
'cm-mw-free-extlink-protocol',
'cm-mw-html-entity',
'cm-mw-link',
'cm-mw-link-pagename',
'cm-mw-tag-nowiki',
'cm-mw-pagename',
'cm-mw-tag-pre',
'cm-mw-section',
'cm-mw-skipformatting',
'cm-mw-strong',
'cm-mw-table-caption',
'cm-mw-templatevariable-delimiter',
// Dynamically generated tags
'cm-mw-ext-ground',
'cm-mw-ext-link-ground',
'cm-mw-ext2-ground',
'cm-mw-ext2-link-ground',
'cm-mw-ext3-ground',
'cm-mw-ext3-link-ground',
'cm-mw-link-ground',
'cm-mw-template-ext-ground',
'cm-mw-template-ext-link-ground',
'cm-mw-template-ext2-ground',
'cm-mw-template-ext2-link-ground',
'cm-mw-template-ext3-ground',
'cm-mw-template-ext3-link-ground',
'cm-mw-template-ground',
'cm-mw-template-link-ground',
'cm-mw-template2-ext-ground',
'cm-mw-template2-ext-link-ground',
'cm-mw-template2-ext2-ground',
'cm-mw-template2-ext2-link-ground',
'cm-mw-template2-ext3-ground',
'cm-mw-template2-ext3-link-ground',
'cm-mw-template2-ground',
'cm-mw-template2-link-ground',
'cm-mw-template3-ext-ground',
'cm-mw-template3-ext-link-ground',
'cm-mw-template3-ext2-ground',
'cm-mw-template3-ext2-link-ground',
'cm-mw-template3-ext3-ground',
'cm-mw-template3-ext3-link-ground',
'cm-mw-template3-ground',
'cm-mw-template3-link-ground',
/** Added by the MW config stub above {@link mwLang} */
'cm-mw-tag-ref',
'cm-mw-ext-ref',
'cm-mw-tag-references',
'cm-mw-ext-references',
'cm-mw-tag-myextension',
'cm-mw-ext-myextension'
] );
} );
} );