diff --git a/GANReviewTool/modules/GANReviewWikicodeGenerator.js b/GANReviewTool/modules/GANReviewWikicodeGenerator.js
index 40553fc..7a99159 100644
--- a/GANReviewTool/modules/GANReviewWikicodeGenerator.js
+++ b/GANReviewTool/modules/GANReviewWikicodeGenerator.js
@@ -1,4 +1,3 @@
-/* eslint-disable indent */
import { TemplateFinder } from './TemplateFinder.js';
export class GANReviewWikicodeGenerator {
@@ -101,20 +100,27 @@ export class GANReviewWikicodeGenerator {
changeGANomineeTemplateStatus( talkWikicode, newStatus ) {
// already has correct status
- const regex = new RegExp( `({{GA nominee[^\\}]*\\|\\s*status\\s*=\\s*${ newStatus })`, 'i' );
- const alreadyHasCorrectStatus = talkWikicode.match( regex );
+ const templateFinder = new TemplateFinder( talkWikicode );
+ const templates = templateFinder.getTemplates( 'GA nominee' );
+ const templatesWithStatus = templates.filter( ( template ) => template.getArgs( 'status' ).size );
+ const alreadyHasCorrectStatus = templatesWithStatus.some( ( template ) => {
+ const value = template.getValue( 'status' );
+ return value && value.toLowerCase() === newStatus.toLowerCase();
+ } );
if ( alreadyHasCorrectStatus ) {
return talkWikicode;
}
// has a status, but needs to be changed
- const hasStatus = talkWikicode.match( /({{GA nominee[^}]*\|\s*status\s*=\s*)[^}|]*/i );
+ const hasStatus = templatesWithStatus.length > 0;
if ( hasStatus ) {
- return talkWikicode.replace( /({{GA nominee[^}]*\|\s*status\s*=\s*)[^}|]*/i, `$1${ newStatus }` );
+ templatesWithStatus[ 0 ].setValue( 'status', newStatus );
+ return templateFinder.getWikitext();
}
// if no old status, insert new status
- return talkWikicode.replace( /({{GA nominee[^}]*)(}})/i, `$1|status=${ newStatus }$2` );
+ templates[ 0 ].setValue( 'status', newStatus );
+ return templateFinder.getWikitext();
}
getLogMessageToAppend( username, action, reviewTitle, reviewRevisionID, talkRevisionID, gaRevisionID, error ) {
@@ -185,12 +191,9 @@ export class GANReviewWikicodeGenerator {
| status =
| result = ${ result }
}}`;
- const hasH2 = wikicode.match( /^==[^=]+==$/m );
- if ( hasH2 ) {
- wikicode = wikicode.replace( /^(.*?==[^=]+==\n)(.*)$/s, '$1' + prependText + '\n$2' );
- } else {
- wikicode = prependText + '\n' + wikicode;
- }
+ const templateFinder = new TemplateFinder( wikicode );
+ templateFinder.placeATOP( prependText, [ 2 ] );
+ wikicode = templateFinder.getWikitext();
// place bottom piece at end
const appendText = '{{abot}}';
@@ -209,15 +212,16 @@ export class GANReviewWikicodeGenerator {
}
getTemplateParameter( wikicode, templateName, parameterName ) {
- templateName = this.regExEscape( templateName );
- parameterName = this.regExEscape( parameterName );
- const regex = new RegExp( `\\{\\{${ templateName }[^\\}]+\\|\\s*${ parameterName }\\s*=\\s*([^\\}\\|]+)\\s*[^\\}]*\\}\\}`, 'i' );
- const parameterValue = wikicode.match( regex );
- if ( Array.isArray( parameterValue ) && parameterValue[ 1 ] !== undefined ) {
- return parameterValue[ 1 ].trim();
- } else {
- return null;
+ const templateFinder = new TemplateFinder( wikicode );
+ const templates = templateFinder.getTemplates( templateName );
+ parameterName = parameterName.toLowerCase();
+ for ( const template of templates ) {
+ const parameter = template.getAllArgs().find( ( { name } ) => name.toLowerCase() === parameterName );
+ if ( parameter ) {
+ return parameter.getValue();
+ }
}
+ return null;
}
/**
@@ -228,7 +232,9 @@ export class GANReviewWikicodeGenerator {
}
deleteGANomineeTemplate( talkWikicode ) {
- return talkWikicode.replace( /\{\{GA nominee[^}]+\}\}\n?/i, '' );
+ const templateFinder = new TemplateFinder( talkWikicode );
+ templateFinder.deleteTemplate( 'GA nominee' );
+ return templateFinder.getWikitext();
}
addGATemplate( talkWikicode, topic, gaPageNumber, oldid ) {
@@ -272,51 +278,9 @@ export class GANReviewWikicodeGenerator {
* @param {string} codeToAdd
*/
addWikicodeAfterTemplates( wikicode, templates, codeToAdd ) {
- /* Started to write a lexer that would solve the edge case of putting the {{GA}} template too low when the [[MOS:TALKORDER]] is incorrect. It's a lot of work though. Pausing for now.
-
- // Note: the MOS:TALKORDER $templates variable is fed to us as a parameter
- let whitespace = ["\t", "\n", " "];
- let lastTemplateNameBuffer = '';
- let currentTemplateNameBuffer = '';
- let templateNestingCount = 0;
- for ( i = 0; i < wikicode.length; i++ ) {
- let toCheck = wikicode.slice(i);
- if ( toCheck.startsWith('{{') {
- templateNestingCount++;
- } else if ( toCheck.startsWith('}}') ) {
- templateNestingCount--;
- }
- // TODO: need to build the templateNameBuffer. need to look for termination characters | or }
- */
-
- let insertPosition = 0;
- for ( const template of templates ) {
- // TODO: handle nested templates
- const regex = new RegExp( `{{${ this.regExEscape( template ) }[^\\}]*}}\\n`, 'ig' );
- const endOfTemplatePosition = this.getEndOfStringPositionOfLastMatch( wikicode, regex );
- if ( endOfTemplatePosition > insertPosition ) {
- insertPosition = endOfTemplatePosition;
- }
- }
- return this.insertStringIntoStringAtPosition( wikicode, codeToAdd, insertPosition );
- }
-
- /**
- * @param {string} haystack
- * @param {RegExp} regex /g flag must be set
- * @return {number} endOfStringPosition Returns zero if not found
- */
- getEndOfStringPositionOfLastMatch( haystack, regex ) {
- const matches = [ ...haystack.matchAll( regex ) ];
- const hasMatches = matches.length;
- if ( hasMatches ) {
- const lastMatch = matches[ matches.length - 1 ];
- const lastMatchStartPosition = lastMatch.index;
- const lastMatchStringLength = lastMatch[ 0 ].length;
- const lastMatchEndPosition = lastMatchStartPosition + lastMatchStringLength;
- return lastMatchEndPosition;
- }
- return 0;
+ const templateFinder = new TemplateFinder( wikicode );
+ templateFinder.addWikicodeAfterTemplates( templates, codeToAdd );
+ return templateFinder.getWikitext();
}
changeWikiProjectArticleClassToGA( talkWikicode ) {
@@ -581,6 +545,7 @@ export class GANReviewWikicodeGenerator {
}
hasArticleHistoryTemplate( wikicode ) {
- return Boolean( wikicode.match( /\{\{Article ?history/i ) );
+ const templateFinder = new TemplateFinder( wikicode );
+ return templateFinder.hasTemplate( 'Article ?history' );
}
}
diff --git a/GANReviewTool/modules/GARCloserWikicodeGenerator.js b/GANReviewTool/modules/GARCloserWikicodeGenerator.js
index d93e83f..e837d1d 100644
--- a/GANReviewTool/modules/GARCloserWikicodeGenerator.js
+++ b/GANReviewTool/modules/GARCloserWikicodeGenerator.js
@@ -354,12 +354,9 @@ __TOC__`;
const resultText = result ? `\n| result = ${ result }\n` : '';
const prependText =
`{{atop${ colorCode }${ resultText }}}`;
- const hasH2OrH3 = wikicode.match( /^===?[^=]+===?$/m );
- if ( hasH2OrH3 ) {
- wikicode = wikicode.replace( /^(.*?===?[^=]+===?\n)\n*(.*)$/s, '$1' + prependText + '\n$2' );
- } else {
- wikicode = prependText + '\n' + wikicode;
- }
+ const templateFinder = new TemplateFinder( wikicode );
+ templateFinder.placeATOP( prependText, [ 2, 3 ] );
+ wikicode = templateFinder.getWikitext();
// place bottom piece at end
const appendText = '{{abot}}';
@@ -393,12 +390,13 @@ __TOC__`;
* There's a {{GA}} template that some people use instead of {{Article history}}. If this is present, replace it with {{Article history}}.
*/
convertGATemplateToArticleHistoryIfPresent( talkPageTitle, wikicode ) {
- const hasArticleHistory = Boolean( wikicode.match( /\{\{Article ?history([^}]*)\}\}/gi ) );
+ const templateFinder = new TemplateFinder( wikicode );
+ const hasArticleHistory = templateFinder.hasTemplate( 'Article ?history' );
const gaTemplateWikicode = this.regexGetFirstMatchString( /(\{\{GA[^}]*\}\})/i, wikicode );
if ( !hasArticleHistory && gaTemplateWikicode ) {
// delete {{ga}} template
- wikicode = wikicode.replace( /\{\{GA[^}]*\}\}\n?/i, '' );
- wikicode = wikicode.trim();
+ templateFinder.deleteTemplate( 'GA' );
+ wikicode = templateFinder.getWikitext().trim();
// parse its parameters
// example: |21:00, 12 March 2017 (UTC)|topic=Sports and recreation|page=1|oldid=769997774
diff --git a/GANReviewTool/modules/TemplateFinder.js b/GANReviewTool/modules/TemplateFinder.js
index 08d9db8..df6f19a 100644
--- a/GANReviewTool/modules/TemplateFinder.js
+++ b/GANReviewTool/modules/TemplateFinder.js
@@ -54,4 +54,55 @@ export class TemplateFinder {
}
}
}
+
+ placeATOP( prependText, levels ) {
+ const heading = this.wikiPage.querySelectorAll( 'heading' )
+ .find( ( { level } ) => levels.includes( level ) );
+ if ( heading ) {
+ heading.after( `\n${ prependText }` );
+ const { lastChild } = heading.lastChild;
+ if ( lastChild && lastChild.type === 'text' ) {
+ lastChild.replaceData( lastChild.data.replace( /\n+$/, '' ) );
+ }
+ } else {
+ this.wikiPage.insertAt( prependText + '\n', 0 );
+ }
+ }
+
+ getTemplates( templateNameCaseInsensitive ) {
+ const templateName = `template:${ templateNameCaseInsensitive.toLowerCase().replace( / /g, '_' ) }`;
+ return this.wikiPage.querySelectorAll( 'template' )
+ .filter( ( { name } ) => name.toLowerCase() === templateName );
+ }
+
+ deleteTemplate( templateNameRegExOrArrayCaseInsensitive ) {
+ const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
+ if ( template ) {
+ const { nextSibling } = template;
+ if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
+ nextSibling.deleteData( 0, 1 );
+ }
+ template.remove();
+ }
+ }
+
+ addWikicodeAfterTemplates( templates, codeToAdd ) {
+ const templateNameArray = templates.map( ( name ) => name.toLowerCase().replace( /\s/g, '_' ) );
+ const filter = ( { name } ) => templateNameArray.includes( TemplateFinder.removePrefix( name ).toLowerCase() );
+ const tokens = this.wikiPage.querySelectorAll( 'template' ).filter( filter );
+ if ( tokens.length === 0 ) {
+ this.wikiPage.insertAt( codeToAdd, 0 );
+ } else {
+ const last = tokens[ tokens.length - 1 ];
+ const { nextSibling } = last;
+ if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
+ nextSibling.deleteData( 0, 1 );
+ }
+ last.after( `\n${ codeToAdd }` );
+ }
+ }
+
+ hasTemplate( templateNameRegExOrArrayCaseInsensitive ) {
+ return Boolean( this.firstTemplate( templateNameRegExOrArrayCaseInsensitive ) );
+ }
}
diff --git a/GANReviewTool/tests/GANReviewWikicodeGenerator.test.js b/GANReviewTool/tests/GANReviewWikicodeGenerator.test.js
index c29befd..534228b 100644
--- a/GANReviewTool/tests/GANReviewWikicodeGenerator.test.js
+++ b/GANReviewTool/tests/GANReviewWikicodeGenerator.test.js
@@ -1,6 +1,8 @@
// import * as service from "../modules/GANReviewWikicodeGenerator.js";
const { GANReviewWikicodeGenerator } = require( '../modules/GANReviewWikicodeGenerator.js' );
+/* eslint-disable quotes */
+
// Babel is required to use ES6 module syntax
// Copy package.json and .babelrc from a project that already has this working
// Babel tutorial: https://www.sitepoint.com/babel-beginners-guide/
@@ -1060,6 +1062,28 @@ describe( 'getFailWikicodeForTalkPage(talkWikicode, reviewTitle)', () => {
`;
expect( wg.getFailWikicodeForTalkPage( talkWikicode, reviewTitle, oldid ) ).toBe( output );
} );
+
+ test( 'Should handle nested templates (#209)', () => {
+ const talkWikicode =
+`{{GA nominee|05:47, 28 December 2024 (UTC)|nominator=~{{Smallcaps|[[User:CtasACT|CtasACT]]}}[[User_talkCtasACT|Talk]]{{nbsp}}•{{nbsp}}[[Special:Contributions/CtasACT|Contribs]]|page=1|subtopic=Magazines and print journalism|status=onreview|note=|shortdesc=20th century Ethiopian writer and journalist}}
+{{WikiProject banner shell|class=C|blp=no|listas=Girma|
+{{WikiProject Biography|needs-photo=yes}}
+{{WikiProject Articles for creation|ts=20140219040153|reviewer=Kvng}}
+{{WikiProject Ethiopia}}
+{{WikiProject Journalism}}
+}}`;
+ const reviewTitle = 'Talk:Baalu Girma';
+ const oldid = 1267462501;
+ const output =
+`{{FailedGA|~~~~~|topic=Magazines and print journalism|page=1|oldid=1267462501}}
+{{WikiProject banner shell|class=C|blp=no|listas=Girma|
+{{WikiProject Biography|needs-photo=yes}}
+{{WikiProject Articles for creation|ts=20140219040153|reviewer=Kvng}}
+{{WikiProject Ethiopia}}
+{{WikiProject Journalism}}
+}}`;
+ expect( wg.getFailWikicodeForTalkPage( talkWikicode, reviewTitle, oldid ) ).toBe( output );
+ } );
} );
describe( 'getLogMessageToAppend(username, passOrFail, reviewTitle, reviewRevisionID, talkRevisionID, gaRevisionID, error)', () => {
@@ -1184,6 +1208,16 @@ describe( 'getOnHoldWikicodeForTalkPage(talkWikicode)', () => {
'{{Talk header}}{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=onhold|note=}}{{WikiProject Football}}';
expect( wg.getOnHoldWikicodeForTalkPage( talkWikicode ) ).toBe( output );
} );
+
+ test( 'Should handle nested templates (#209)', () => {
+ const talkWikicode =
+`{{GA nominee|05:34, 10 September 2023 (UTC)|nominator={{colored link|#198754|User:Jake-jakubowski|Jake Jakubowski}} {{colored link|#0d6efd|User_talk:Jake-jakubowski|Talk}}|page=2|subtopic=Transport|status=onreview|note=|shortdesc=Road bridge in Maine, US}}
+{{FailedGA|22:33, 29 August 2023 (UTC)|topic=Transport|page=1|oldid=1171806123}}`;
+ const output =
+`{{GA nominee|05:34, 10 September 2023 (UTC)|nominator={{colored link|#198754|User:Jake-jakubowski|Jake Jakubowski}} {{colored link|#0d6efd|User_talk:Jake-jakubowski|Talk}}|page=2|subtopic=Transport|status=onhold|note=|shortdesc=Road bridge in Maine, US}}
+{{FailedGA|22:33, 29 August 2023 (UTC)|topic=Transport|page=1|oldid=1171806123}}`;
+ expect( wg.getOnHoldWikicodeForTalkPage( talkWikicode ) ).toBe( output );
+ } );
} );
describe( 'getAskSecondOpinionWikicodeForTalkPage(talkWikicode)', () => {
@@ -1195,15 +1229,13 @@ describe( 'getAskSecondOpinionWikicodeForTalkPage(talkWikicode)', () => {
expect( wg.getAskSecondOpinionWikicodeForTalkPage( talkWikicode ) ).toBe( output );
} );
- /*
- test('Should handle nested templates', () => {
- let talkWikicode =
+ test( 'Should handle nested templates', () => {
+ const talkWikicode =
`{{GA nominee|14:41, 19 October 2022 (UTC)|nominator=––[[User:FormalDude|'''Formal'''{{color|black|'''Dude'''}}]] [[User talk:FormalDude|'''(talk)''']]|page=1|subtopic=Politics and government|status=|note=}}`;
- let output =
+ const output =
`{{GA nominee|14:41, 19 October 2022 (UTC)|nominator=––[[User:FormalDude|'''Formal'''{{color|black|'''Dude'''}}]] [[User talk:FormalDude|'''(talk)''']]|page=1|subtopic=Politics and government|status=2ndopinion|note=}}`;
- expect(wg.getAskSecondOpinionWikicodeForTalkPage(talkWikicode)).toBe(output);
- });
- */
+ expect( wg.getAskSecondOpinionWikicodeForTalkPage( talkWikicode ) ).toBe( output );
+ } );
} );
describe( 'getAnswerSecondOpinionWikicodeForTalkPage(talkWikicode)', () => {
@@ -1239,8 +1271,6 @@ blah`;
| status =
| result = Passed
}}
-
-
== Test ==
test
@@ -1358,6 +1388,15 @@ describe( 'deleteGANomineeTemplate(talkWikicode)', () => {
{{Test2}}`;
expect( wg.deleteGANomineeTemplate( talkWikicode ) ).toBe( output );
} );
+
+ test( 'brackets in the signature (#214)', () => {
+ const talkWikicode =
+`{{GA nominee|08:24, 7 January 2024 (UTC)|nominator=[[User talk:Praseodymium-141|141]][[User:Praseodymium-141|Pr]] {[[Special:Contributions/Praseodymium-141|contribs]]}|page=2|subtopic=Chemistry and materials science|status=onreview|note=|shortdesc=}}
+{{Failed GA|17:02, 7 June 2022 (UTC)|page=1|subtopic=Chemistry and materials science}}`;
+ const output =
+'{{Failed GA|17:02, 7 June 2022 (UTC)|page=1|subtopic=Chemistry and materials science}}';
+ expect( wg.deleteGANomineeTemplate( talkWikicode ) ).toBe( output );
+ } );
} );
describe( 'getTemplateParameter(wikicode, templateName, parameterName)', () => {
@@ -2343,32 +2382,51 @@ describe( 'addWikicodeAfterTemplates(wikicode, templates, codeToAdd)', () => {
} );
} );
-describe( 'getEndOfStringPositionOfLastMatch(haystack, regex)', () => {
- test( 'No match', () => {
- const haystack = 'AAA BBB';
- const regex = /ghi/ig;
- const output = 0;
- expect( wg.getEndOfStringPositionOfLastMatch( haystack, regex ) ).toBe( output );
+describe( 'changeGANomineeTemplateStatus( talkWikicode, newStatus )', () => {
+ test( 'already has correct status', () => {
+ const talkWikicode =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=onhold|note=}}';
+ const newStatus = 'onhold';
+ const output =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=onhold|note=}}';
+ expect( wg.changeGANomineeTemplateStatus( talkWikicode, newStatus ) ).toBe( output );
} );
- test( '1 match', () => {
- const haystack = 'Abc def';
- const regex = /Abc/ig;
- const output = 3;
- expect( wg.getEndOfStringPositionOfLastMatch( haystack, regex ) ).toBe( output );
+ test( 'has a blank status', () => {
+ const talkWikicode =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=|note=}}';
+ const newStatus = 'onhold';
+ const output =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=onhold|note=}}';
+ expect( wg.changeGANomineeTemplateStatus( talkWikicode, newStatus ) ).toBe( output );
} );
- test( '2 matches', () => {
- const haystack = 'Abc Abc def';
- const regex = /Abc/ig;
- const output = 7;
- expect( wg.getEndOfStringPositionOfLastMatch( haystack, regex ) ).toBe( output );
+ test( 'has a status, but needs to be changed', () => {
+ const talkWikicode =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=2ndopinion|note=}}';
+ const newStatus = 'onhold';
+ const output =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|status=onhold|note=}}';
+ expect( wg.changeGANomineeTemplateStatus( talkWikicode, newStatus ) ).toBe( output );
} );
- test( 'Case insensitive', () => {
- const haystack = 'Abc Abc def';
- const regex = /abc/ig;
- const output = 7;
- expect( wg.getEndOfStringPositionOfLastMatch( haystack, regex ) ).toBe( output );
+ test( 'no old status, insert new status', () => {
+ const talkWikicode =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|note=}}';
+ const newStatus = 'onhold';
+ const output =
+'{{GA nominee|23:46, 28 June 2022 (UTC)|nominator=[[User:TonyTheTiger|TonyTheTiger]] ([[User talk:TonyTheTiger|T]] / [[Special:Contributions/TonyTheTiger|C]] / [[WP:FOUR]] / [[WP:CHICAGO]] / [[WP:WAWARD]])|page=1|subtopic=Sports and recreation|note=|status=onhold}}';
+ expect( wg.changeGANomineeTemplateStatus( talkWikicode, newStatus ) ).toBe( output );
+ } );
+
+ test( 'Should handle nested templates (#209)', () => {
+ const talkWikicode =
+`{{GA nominee|05:34, 10 September 2023 (UTC)|nominator={{colored link|#198754|User:Jake-jakubowski|Jake Jakubowski}} {{colored link|#0d6efd|User_talk:Jake-jakubowski|Talk}}|page=2|subtopic=Transport|status=onreview|note=|shortdesc=Road bridge in Maine, US}}
+{{FailedGA|22:33, 29 August 2023 (UTC)|topic=Transport|page=1|oldid=1171806123}}`;
+ const newStatus = 'onhold';
+ const output =
+`{{GA nominee|05:34, 10 September 2023 (UTC)|nominator={{colored link|#198754|User:Jake-jakubowski|Jake Jakubowski}} {{colored link|#0d6efd|User_talk:Jake-jakubowski|Talk}}|page=2|subtopic=Transport|status=onhold|note=|shortdesc=Road bridge in Maine, US}}
+{{FailedGA|22:33, 29 August 2023 (UTC)|topic=Transport|page=1|oldid=1171806123}}`;
+ expect( wg.changeGANomineeTemplateStatus( talkWikicode, newStatus ) ).toBe( output );
} );
} );
diff --git a/GANReviewTool/tests/GARCloserWikicodeGenerator.test.js b/GANReviewTool/tests/GARCloserWikicodeGenerator.test.js
index 629ca15..0498a39 100644
--- a/GANReviewTool/tests/GARCloserWikicodeGenerator.test.js
+++ b/GANReviewTool/tests/GARCloserWikicodeGenerator.test.js
@@ -1763,3 +1763,101 @@ describe( 'firstTemplateDeleteParameter(wikicode, template, parameter)', () => {
expect( wg.firstTemplateDeleteParameter( wikicode, template, parameter ) ).toBe( output );
} );
} );
+
+describe( 'placeATOP(wikicode, result, color)', () => {
+ test( 'h2 present', () => {
+ const result = 'Passed';
+ const color = 'green';
+ const wikicode =
+`
+
+== Test ==
+
+
+== Test ==
+
+test
+
+blah`;
+ const output =
+`== Test ==
+{{atopg
+| result = Passed
+}}
+== Test ==
+
+test
+
+blah
+{{abot}}
+`;
+ expect( wg.placeATOP( wikicode, result, color ) ).toBe( output );
+ } );
+
+ test( 'h3 present', () => {
+ const result = 'Passed';
+ const color = 'green';
+ const wikicode =
+`
+
+=== Test ===
+
+
+== Test ==
+
+test
+
+blah`;
+ const output =
+`=== Test ===
+{{atopg
+| result = Passed
+}}
+== Test ==
+
+test
+
+blah
+{{abot}}
+`;
+ expect( wg.placeATOP( wikicode, result, color ) ).toBe( output );
+ } );
+
+ test( 'h2/h3 absent', () => {
+ const result = 'Passed';
+ const color = 'green';
+ const wikicode =
+`test
+
+blah`;
+ const output =
+`{{atopg
+| result = Passed
+}}
+test
+
+blah
+{{abot}}
+`;
+ expect( wg.placeATOP( wikicode, result, color ) ).toBe( output );
+ } );
+
+ test( 'failed instead of passed', () => {
+ const result = 'Failed';
+ const color = 'red';
+ const wikicode =
+`test
+
+blah`;
+ const output =
+`{{atopr
+| result = Failed
+}}
+test
+
+blah
+{{abot}}
+`;
+ expect( wg.placeATOP( wikicode, result, color ) ).toBe( output );
+ } );
+} );
diff --git a/GANReviewTool/tests/TemplateFinder.test.js b/GANReviewTool/tests/TemplateFinder.test.js
index 77c0ad6..419f769 100644
--- a/GANReviewTool/tests/TemplateFinder.test.js
+++ b/GANReviewTool/tests/TemplateFinder.test.js
@@ -66,3 +66,236 @@ describe( 'firstTemplate( templateNameRegExOrArrayCaseInsensitive )', () => {
expect( TemplateFinder.removePrefix( ( tf.firstTemplate( templateNameRegExOrArrayCaseInsensitive ) ).name ) ).toBe( output );
} );
} );
+
+describe( 'firstTemplateInsertCode( templateNameRegExOrArrayCaseInsensitive, codeToInsert )', () => {
+ it( 'Should do nothing if template not found', () => {
+ const wikitext = 'Hi';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const codeToInsert = '|parameter=value';
+ const output = 'Hi';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateInsertCode( templateNameRegExOrArrayCaseInsensitive, codeToInsert );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should insert code into first template found', () => {
+ const wikitext = 'Hi{{First}}{{Second}}';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const codeToInsert = '|parameter=value';
+ const output = 'Hi{{First|parameter=value\n}}{{Second}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateInsertCode( templateNameRegExOrArrayCaseInsensitive, codeToInsert );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should insert code into specific template found', () => {
+ const wikitext = 'Hi{{First}}{{Second}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'Second';
+ const codeToInsert = '|parameter=value';
+ const output = 'Hi{{First}}{{Second|parameter=value\n}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateInsertCode( templateNameRegExOrArrayCaseInsensitive, codeToInsert );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+} );
+
+describe( 'firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter )', () => {
+ test( 'No template', () => {
+ const wikitext = 'Hi';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const parameter = 'parameter';
+ const output = null;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) ).toBe( output );
+ } );
+
+ test( 'No parameter', () => {
+ const wikitext = 'Hi{{First}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const parameter = 'parameter';
+ const output = null;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) ).toBe( output );
+ } );
+
+ test( 'With parameter', () => {
+ const wikitext = 'Hi{{First|parameter = value}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const parameter = 'parameter';
+ const output = 'value';
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) ).toBe( output );
+ } );
+
+ test( 'Multiple templates - No parameter', () => {
+ const wikitext = 'Hi{{First}}{{Second|parameter = value2}}';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const parameter = 'parameter';
+ const output = null;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) ).toBe( output );
+ } );
+
+ test( 'Multiple templates - With parameter', () => {
+ const wikitext = 'Hi{{First|parameter = value1}}{{Second|parameter = value2}}';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const parameter = 'parameter';
+ const output = 'value1';
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) ).toBe( output );
+ } );
+} );
+
+describe( 'firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter )', () => {
+ it( 'Should do nothing if template not found', () => {
+ const wikitext = 'Hi';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const parameter = 'parameter';
+ const output = 'Hi';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should do nothing if parameter not found', () => {
+ const wikitext = 'Hi{{First|other=value}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const parameter = 'parameter';
+ const output = 'Hi{{First|other=value}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should delete parameter from first template found', () => {
+ const wikitext = 'Hi{{First|parameter=value|other=value2}}{{Second|parameter=value3}}';
+ const templateNameRegExOrArrayCaseInsensitive = '';
+ const parameter = 'parameter';
+ const output = 'Hi{{First|other=value2}}{{Second|parameter=value3}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should delete parameter from specific template found', () => {
+ const wikitext = 'Hi{{First|parameter=value}}{{Second|parameter=value2|other=value3}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'Second';
+ const parameter = 'parameter';
+ const output = 'Hi{{First|parameter=value}}{{Second|other=value3}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+} );
+
+describe( 'placeATOP( prependText, levels )', () => {
+ it( 'Should prepend text if no headings found', () => {
+ const wikitext = 'Hi';
+ const prependText = 'Hello';
+ const levels = [ 2 ];
+ const output = 'Hello\nHi';
+ const tf = new TemplateFinder( wikitext );
+ tf.placeATOP( prependText, levels );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should prepend text if no matching headings found', () => {
+ const wikitext = 'Hi\n== Heading ==';
+ const prependText = 'Hello';
+ const levels = [ 3 ];
+ const output = 'Hello\nHi\n== Heading ==';
+ const tf = new TemplateFinder( wikitext );
+ tf.placeATOP( prependText, levels );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should insert text after first matching heading found', () => {
+ const wikitext = 'Hi\n== Heading 2 ==\n=== Heading 3 ===';
+ const prependText = 'Hello';
+ const levels = [ 3 ];
+ const output = 'Hi\n== Heading 2 ==\n=== Heading 3 ===\nHello';
+ const tf = new TemplateFinder( wikitext );
+ tf.placeATOP( prependText, levels );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should insert text after first matching heading found (multiple candidates)', () => {
+ const wikitext = 'Hi\n== Heading 2 ==\n=== Heading 3 ===';
+ const prependText = 'Hello';
+ const levels = [ 2, 3 ];
+ const output = 'Hi\n== Heading 2 ==\nHello\n=== Heading 3 ===';
+ const tf = new TemplateFinder( wikitext );
+ tf.placeATOP( prependText, levels );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+} );
+
+describe( 'getTemplates( templateNameCaseInsensitive )', () => {
+ it( 'Should return all matching templates', () => {
+ const wikitext = 'Hi{{First}}{{Second}}{{first}}';
+ const templateNameCaseInsensitive = 'First';
+ const output = 2;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.getTemplates( templateNameCaseInsensitive ).length ).toBe( output );
+ } );
+} );
+
+describe( 'deleteTemplate( templateNameRegExOrArrayCaseInsensitive )', () => {
+ it( 'Should do nothing if template not found', () => {
+ const wikitext = 'Hi';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const output = 'Hi';
+ const tf = new TemplateFinder( wikitext );
+ tf.deleteTemplate( templateNameRegExOrArrayCaseInsensitive );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should delete first matching template found', () => {
+ const wikitext = 'Hi{{First}}\n{{Second}}\n{{First}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const output = 'Hi{{Second}}\n{{First}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.deleteTemplate( templateNameRegExOrArrayCaseInsensitive );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+} );
+
+describe( 'addWikicodeAfterTemplates( templates, codeToAdd )', () => {
+ it( 'Should add code at the top if no templates found', () => {
+ const wikitext = 'Hi';
+ const templates = [ 'First', 'Second' ];
+ const codeToAdd = '{{Added}}';
+ const output = '{{Added}}Hi';
+ const tf = new TemplateFinder( wikitext );
+ tf.addWikicodeAfterTemplates( templates, codeToAdd );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+
+ it( 'Should add code after last matching template found', () => {
+ const wikitext = 'Hi{{First}}\n{{Second}}';
+ const templates = [ 'First', 'Second' ];
+ const codeToAdd = '{{Added}}';
+ const output = 'Hi{{First}}\n{{Second}}\n{{Added}}';
+ const tf = new TemplateFinder( wikitext );
+ tf.addWikicodeAfterTemplates( templates, codeToAdd );
+ expect( tf.getWikitext() ).toBe( output );
+ } );
+} );
+
+describe( 'hasTemplate( templateNameRegExOrArrayCaseInsensitive )', () => {
+ test( 'No template', () => {
+ const wikitext = 'Hi';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const output = false;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.hasTemplate( templateNameRegExOrArrayCaseInsensitive ) ).toBe( output );
+ } );
+
+ test( 'Template exists', () => {
+ const wikitext = 'Hi{{First}}';
+ const templateNameRegExOrArrayCaseInsensitive = 'First';
+ const output = true;
+ const tf = new TemplateFinder( wikitext );
+ expect( tf.hasTemplate( templateNameRegExOrArrayCaseInsensitive ) ).toBe( output );
+ } );
+} );