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 ); + } ); +} );