'use strict';

const ipReveal = require( 'ext.checkUser.tempAccounts/ipReveal.js' );
const { makeTempUserLink, waitUntilElementDisappears } = require( './utils.js' );
const ipRevealUtils = require( 'ext.checkUser.tempAccounts/ipRevealUtils.js' );

const originalGetAutoRevealStatus = ipRevealUtils.getAutoRevealStatus;
let server;

// Define user names that can be used in these tests, which are removed after each test run.
const tempName1 = '~1';
const tempName2 = '~2';
const tempName3 = '~3';

QUnit.module( 'ext.checkUser.tempAccounts.ipReveal', QUnit.newMwEnvironment( {
	beforeEach: function () {
		this.server = this.sandbox.useFakeServer();
		this.server.respondImmediately = true;
		server = this.server;
	},
	afterEach: function () {
		server.restore();
		ipRevealUtils.getAutoRevealStatus = originalGetAutoRevealStatus;

		// Ensure no users are set to pre-revealed
		mw.storage.remove( 'mw-checkuser-temp-' + tempName1 );
		mw.storage.remove( 'mw-checkuser-temp-' + tempName2 );
		mw.storage.remove( 'mw-checkuser-temp-' + tempName3 );
	},
	config: {
		// Prevent initOnLoad.js from running automatically and then
		// calling enableMultiReveal on the document.
		wgCanonicalSpecialPageName: 'CheckUser'
	}
} ) );

QUnit.test( 'Test getRevisionId', ( assert ) => {
	// Create an element with a child that has the data-mw-revid attribute.
	const elementWithRevId = document.createElement( 'div' );
	const childWithRevId = document.createElement( 'span' );
	childWithRevId.setAttribute( 'data-mw-revid', '123456' );
	elementWithRevId.appendChild( childWithRevId );
	// Call getRevisionId with the newly created element, to test when the
	// element is a revision line.
	assert.strictEqual(
		ipReveal.getRevisionId( $( childWithRevId ) ),
		123456,
		'getRevisionId return value for element with data-mw-revid attribute'
	);
	// Call getRevisionId with an element that does not have data-mw-revid, to test
	// when the element is not a revision line.
	assert.strictEqual(
		ipReveal.getRevisionId( $( document.createElement( 'div' ) ) ),
		undefined,
		'getRevisionId return value for element without data-mw-revid attribute'
	);
} );

QUnit.test( 'Test getLogId', ( assert ) => {
	// Create an element with a child that has the data-mw-logid attribute.
	const elementWithRevId = document.createElement( 'div' );
	const childWithRevId = document.createElement( 'span' );
	childWithRevId.setAttribute( 'data-mw-logid', '123456' );
	elementWithRevId.appendChild( childWithRevId );
	// Call getLogId with the newly created element, to test when the
	// element is a log line.
	assert.strictEqual(
		ipReveal.getLogId( $( childWithRevId ) ),
		123456,
		'getLogId return value for element with data-mw-logid attribute'
	);
	// Call getLogId with an element that does not have data-mw-logid, to test
	// when the element is not a log line.
	assert.strictEqual(
		ipReveal.getLogId( $( document.createElement( 'div' ) ) ),
		undefined,
		'getLogId return value for element without data-mw-logid attribute'
	);
} );

QUnit.test( 'Test enableMultiReveal', ( assert ) => {
	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );
	// Add some testing revision lines to test the multi reveal functionality.
	const revisionLines = { 1: tempName1, 2: tempName1, 3: tempName2 };
	Object.entries( revisionLines ).forEach( ( [ revId, username ] ) => {
		const $revisionLine = $( '<div>' ).attr( 'data-mw-revid', revId );
		$qunitFixture.append( $revisionLine );
		// Add the temporary account username link
		$revisionLine.append( makeTempUserLink( username ) );
		// Add the Show IP button manually for the temporary account username link.
		$revisionLine.append( ipReveal.makeButton(
			username,
			{ targetId: revId, allIds: [ revId ] },
			{},
			{},
			undefined,
			$qunitFixture
		) );
		assert.strictEqual(
			$revisionLine.find( '.ext-checkuser-tempaccount-reveal-ip-button' ).text(),
			'(checkuser-tempaccount-reveal-ip-button-label)',
			'IP reveal button for revId ' + revId + ' before multi reveal'
		);
	} );
	// Enable multi reveal on the QUnit fixture.
	ipReveal.enableMultiReveal( $qunitFixture );

	// Fire the userRevealed event with the ~1 temporary account username
	// and the IP addresses for the revisions.
	const isRevisionLookup = true;
	const isLogLookup = false;
	const isAbuseFilterLookup = false;
	const ips = {
		1: '127.0.0.1',
		2: '127.0.0.2',
		3: '127.0.0.3'
	};
	const batchResponse = {
		[ tempName1 ]: { revIps: ips, expired: { revIds: [], logIps: [] } }
	};

	$qunitFixture.trigger(
		'userRevealed',
		[
			tempName1,
			ips,
			isRevisionLookup,
			isLogLookup,
			isAbuseFilterLookup,
			undefined,
			batchResponse
		]
	);
	// Check that the IP addresses were added to the buttons for the revisions
	// with the ~1 temporary account username.
	assert.strictEqual(
		$qunitFixture.find( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
		'127.0.0.1',
		'IP reveal button for revId 1 before multi reveal'
	);
	assert.strictEqual(
		$qunitFixture.find( '[data-mw-revid="2"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
		'127.0.0.2',
		'IP reveal button for revId 2 before multi reveal'
	);
	assert.strictEqual(
		$qunitFixture.find(
			'[data-mw-revid="3"] .ext-checkuser-tempaccount-reveal-ip-button'
		).text(),
		'(checkuser-tempaccount-reveal-ip-button-label)',
		'IP reveal button for revId 3 before multi reveal'
	);
} );

QUnit.test( 'Test enableMultiReveal with mixed wikiUrl values', function ( assert ) {
	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );

	// Add some testing revision lines to test the multi-reveal functionality with mixed wikiUrl
	const revisionLines = { 1: 'test/$1', 2: undefined };
	Object.entries( revisionLines ).forEach( ( [ revId, wikiUrl ] ) => {
		const $revisionLine = $( '<div>' )
			.attr( 'data-mw-revid', revId )
			.attr( 'data-wiki-url', wikiUrl );
		$qunitFixture.append( $revisionLine );
		// Add the temporary account user link
		$revisionLine.append( makeTempUserLink( tempName1, wikiUrl ) );
	} );

	ipReveal.addIpRevealButtons( $qunitFixture );
	ipReveal.enableMultiReveal( $qunitFixture );

	// Set up spies on the buttons
	const spies = {};
	const sandbox = this.sandbox;
	Object.keys( revisionLines ).forEach( ( revId ) => {
		const $button = $qunitFixture.find( `[data-mw-revid="${ revId }"] .ext-checkuser-tempaccount-reveal-ip-button` );
		spies[ revId ] = sandbox.spy();
		$button.on( 'revealIp', spies[ revId ] );
	} );

	// Fire the userRevealed event with some data. The response includes extra data to ensure that
	// it is not added to user link with the wrong URL.
	const isRevisionLookup = true;
	const isLogLookup = false;
	const isAbuseFilterLookup = false;
	const ips = {
		1: '127.0.0.1',
		2: '127.0.0.2'
	};
	const batchResponse = {
		[ tempName1 ]: { revIps: ips, expired: { revIds: [], logIps: [] } }
	};
	const eventWikiUrl = 'test/$1';

	$qunitFixture.trigger(
		'userRevealed',
		[
			tempName1,
			ips,
			isRevisionLookup,
			isLogLookup,
			isAbuseFilterLookup,
			eventWikiUrl,
			batchResponse
		]
	);

	// Check that the IP addresses were added correctly
	assert.strictEqual(
		$qunitFixture.find( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
		'127.0.0.1',
		'IP reveal button 1 replaced with IP (matching wikiUrl)'
	);
	assert.strictEqual(
		$qunitFixture.find(
			'[data-mw-revid="2"] .ext-checkuser-tempaccount-reveal-ip-button'
		).text(),
		'(checkuser-tempaccount-reveal-ip-button-label)',
		'IP reveal button for revId 2 not replaced (non-matching wikiUrl)'
	);

	// Check that trigger was called on the button that did not match the wikiUrl
	assert.strictEqual(
		spies[ 1 ].called,
		false,
		'trigger not called on button 1 (matching wikiUrl)'
	);
	assert.strictEqual(
		spies[ 2 ].calledOnce,
		true,
		'trigger called on button 2 (non-matching wikiUrl)'
	);
} );

QUnit.test( 'Test enableMultiReveal with grouped recent changes', ( assert ) => {
	const done = assert.async();

	server.respond( ( request ) => {
		if ( !request.url.includes( '/batch-temporaryaccount' ) ) {
			assert.expect( 0 );
			done( new Error( `Unexpected request URL: ${ request.url }` ) );
		}

		request.respond(
			200,
			{ 'Content-Type': 'application/json' },
			JSON.stringify( {
				[ tempName1 ]: {
					// eslint-disable-next-line quote-props
					revIps: { '1': '127.0.0.1' },
					// eslint-disable-next-line quote-props
					logIps: { '1': '127.0.0.1' },
					// eslint-disable-next-line quote-props
					abuseLogIps: { '1': '127.0.0.4' },
					lastUsedIp: '127.0.0.3'
				}
			} )
		);
	} );
	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );
	// Add a revision line and a line with no ID for the same user, mimicking grouped recent changes
	const lines = [
		{
			$element: $( '<div>' ).attr( 'data-mw-revid', 1 ),
			revIds: { targetId: 1, allIds: [ 1 ] },
			logIds: {},
			aflIds: {}
		},
		{
			$element: $( '<div>' ).attr( 'data-mw-logid', 1 ),
			revIds: {},
			logIds: { targetId: 1, allIds: [ 1 ] },
			aflIds: {}
		},
		{
			$element: $( '<div>' ).attr( 'data-afl-log-id', 1 ),
			revIds: {},
			logIds: {},
			aflIds: { targetId: 1, allIds: [ 1 ] }
		},
		{
			$element: $( '<div>' ).addClass( 'no-id' ),
			revIds: {},
			logIds: {},
			aflIds: {}
		}
	];
	lines.forEach( ( line ) => {
		const username = tempName1;
		$qunitFixture.append( line.$element );
		// Add the temporary account username link
		line.$element.append( makeTempUserLink( username ) );
		// Add the Show IP button manually for the temporary account username link.
		line.$element.append( ipReveal.makeButton(
			username,
			line.revIds,
			line.logIds,
			line.aflIds,
			undefined,
			$qunitFixture
		) );
	} );
	// Enable multi reveal on the QUnit fixture.
	ipReveal.enableMultiReveal( $qunitFixture );

	$( '.no-id .ext-checkuser-tempaccount-reveal-ip-button a', $qunitFixture )[ 0 ].click();

	waitUntilElementDisappears( '.no-id .ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
		// Verify that the button has gone and was replaced with the IP
		assert.strictEqual(
			$( '.no-id .ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
			0,
			'Button removed after click'
		);
		const $revealedLatestIp = $( '.ext-checkuser-tempaccount-reveal-ip', $qunitFixture );
		assert.strictEqual(
			$revealedLatestIp.text(),
			'127.0.0.3',
			'Text of element that replaced button'
		);
		assert.strictEqual(
			$revealedLatestIp.children().attr( 'title' ),
			'(checkuser-tempaccount-reveal-ip-tooltip-latest)',
			'Tooltip for revealed IP'
		);

		waitUntilElementDisappears( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
			// Verify that the button has gone and was replaced with the IP
			assert.strictEqual(
				$( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
				0,
				'Button removed after click'
			);
			const $revealedRevisionIp = $( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip', $qunitFixture );
			assert.strictEqual(
				$revealedRevisionIp.text(),
				'127.0.0.1',
				'Text of element that replaced button'
			);
			assert.strictEqual(
				$revealedRevisionIp.children().attr( 'title' ),
				'(checkuser-tempaccount-reveal-ip-tooltip-revision)',
				'Tooltip for revealed IP'
			);

			waitUntilElementDisappears( '[data-mw-logid="1"] .ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
				// Verify that the button has gone and was replaced with the IP
				assert.strictEqual(
					$( '[data-mw-logid="1"] .ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
					0,
					'Button removed after click'
				);
				const $revealedActionIp = $( '[data-mw-logid="1"] .ext-checkuser-tempaccount-reveal-ip', $qunitFixture );
				assert.strictEqual(
					$revealedActionIp.text(),
					'127.0.0.1',
					'Text of element that replaced button'
				);
				assert.strictEqual(
					$revealedActionIp.children().attr( 'title' ),
					'(checkuser-tempaccount-reveal-ip-tooltip-action)',
					'Tooltip for revealed IP'
				);

				done();
			} );
		} );
	} );
} );

QUnit.test( 'Test addIpRevealButtons adds temporary account IP reveal buttons', ( assert ) => {
	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );
	const temporaryAccountUserLinks = [];
	// Add some testing revision lines
	const revisionLines = { 1: tempName1, 2: tempName1, 3: tempName2 };
	Object.entries( revisionLines ).forEach( ( [ revId, username ] ) => {
		const $revisionLine = $( '<div>' ).attr( 'data-mw-revid', revId );
		$qunitFixture.append( $revisionLine );
		// Add the temporary account username link for the revision line
		const $tempAccountUserLink = makeTempUserLink( username );
		$revisionLine.append( $tempAccountUserLink );
		temporaryAccountUserLinks.push( $tempAccountUserLink );
	} );
	// Add some testing log lines
	const logLines = { 2: tempName1, 5: tempName2, 6: tempName3 };
	Object.entries( logLines ).forEach( ( [ logId, username ] ) => {
		const $logLine = $( '<div>' ).attr( 'data-mw-logid', logId );
		$qunitFixture.append( $logLine );
		// Add the temporary account username link for the log line
		const $tempAccountUserLink = makeTempUserLink( username );
		$logLine.append( $tempAccountUserLink );
		temporaryAccountUserLinks.push( $tempAccountUserLink );
	} );
	// Add a temporary account username that is not associated with a revision or log
	const $tempAccountUserLink = makeTempUserLink( tempName3 );
	$qunitFixture.append( $tempAccountUserLink );
	temporaryAccountUserLinks.push( $tempAccountUserLink );
	// Add a temporary account username that has no data-mw-target
	mw.config.set( 'wgCheckUserContribsPageLocalName', 'Contributions' );
	const $noTargetUserLink = $( '<a>' )
		.addClass( 'mw-tempuserlink' )
		.text( tempName1 )
		.attr( 'href', mw.util.getUrl( 'Special:Contributions/' + tempName1 ) );
	const $parserOutput = $( '<div>' ).addClass( 'mw-parser-output' );
	$parserOutput.append( $noTargetUserLink );
	$qunitFixture.append( $parserOutput );
	temporaryAccountUserLinks.push( $noTargetUserLink );
	// Verify that before the call to ::addIpRevealButtons there are no Show IP buttons
	assert.strictEqual(
		$qunitFixture.find( '.ext-checkuser-tempaccount-reveal-ip-button' ).length,
		0,
		'No IP reveal links before addIpRevealButtons call'
	);
	// Call addIpRevealButtons on the QUnit fixture
	ipReveal.addIpRevealButtons( $qunitFixture );
	// Call again to ensure that the buttons can only be added once
	ipReveal.addIpRevealButtons( $qunitFixture );
	// Verify that the Show IP button was added for all temporary user links
	temporaryAccountUserLinks.forEach( ( $element ) => {
		assert.strictEqual(
			// eslint-disable-next-line no-jquery/no-class-state
			$element.next().hasClass( 'ext-checkuser-tempaccount-reveal-ip-button' ), true,
			'IP reveal button is directly after temporary account user link after addIpRevealButtons call'
		);
		assert.strictEqual(
			// eslint-disable-next-line no-jquery/no-class-state
			$element.next().next().hasClass( 'ext-checkuser-tempaccount-reveal-ip-button' ), false,
			'Only one IP reveal button is added after multiple addIpRevealButtons calls'
		);
	} );
} );

QUnit.test( 'Test makeButton creates expected button', ( assert ) => {
	// Call the method under test
	const elements = ipReveal.makeButton( tempName1, { targetId: 1, allIds: [ 1 ] }, {} );
	const [ $button ] = elements;

	assert.strictEqual( elements.length, 1, 'Only the IP reveal button should be returned for non-blocked users' );

	// Verify that the button has the expected button text and classes
	assert.strictEqual(
		$button.text(),
		'(checkuser-tempaccount-reveal-ip-button-label)',
		'Button text'
	);
	const expectedClasses = [
		'ext-checkuser-tempaccount-reveal-ip-button',
		'oo-ui-flaggedElement-progressive',
		'oo-ui-buttonElement'
	];
	expectedClasses.forEach( ( className ) => {
		assert.strictEqual(
			// eslint-disable-next-line no-jquery/no-class-state
			$button.hasClass( className ),
			true,
			'Button has ' + className + ' class'
		);
	} );
} );

QUnit.test( 'Test makeButton creates expected button for blocked performer', ( assert ) => {
	mw.config.set( 'wgCheckUserIsPerformerBlocked', true );
	const elements = ipReveal.makeButton( tempName1, { targetId: 1, allIds: [ 1 ] }, {} );
	const [ $button, $blockInfoWidget ] = elements;

	assert.strictEqual(
		elements.length,
		2,
		'Both an IP reveal button and a block details button should be returned for non-blocked users'
	);

	// Verify that the button has the expected button text and classes
	assert.strictEqual(
		$button.text(),
		'(checkuser-tempaccount-reveal-ip-button-label)',
		'Button text'
	);
	assert.strictEqual(
		$blockInfoWidget.find( '[role=button]' ).attr( 'title' ),
		'(checkuser-tempaccount-reveal-blocked-title)',
		'Block info widget title'
	);
} );

QUnit.test( 'Test makeButton creates expected button for external temporary user', ( assert ) => {
	// Call the method under test
	const elements = ipReveal.makeButton( tempName1, { targetId: 1, allIds: [ 1 ] }, {}, {}, 'testUrl' );
	const [ $button ] = elements;

	assert.strictEqual(
		$button.data( 'wikiUrl' ),
		'testUrl',
		'Button external URL data'
	);
} );

function performMakeButtonRequestTest(
	assert,
	responseCode,
	responseContent,
	expectedText,
	expectedTitle
) {
	// We need the test to wait a small amount of time for the click events to finish.
	const done = assert.async();
	server.respond( ( request ) => {
		request.respond( responseCode, { 'Content-Type': 'application/json' }, responseContent );
	} );
	// Call the method under test to get a button
	const $button = ipReveal.makeButton( tempName1, { targetId: 1, allIds: [ 1 ] }, {} );
	// Add the button to the QUnit fixture
	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );
	$qunitFixture.append( $button );
	// Click the button
	$( '.ext-checkuser-tempaccount-reveal-ip-button a', $qunitFixture )[ 0 ].click();
	waitUntilElementDisappears( '.ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
		// Verify that the button has gone and was replaced with a span containing an error message
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
			0,
			'Button removed after click'
		);
		const $revealedIp = $( '.ext-checkuser-tempaccount-reveal-ip', $qunitFixture );
		assert.strictEqual(
			$revealedIp.text(),
			expectedText,
			'Text of element that replaced button'
		);
		assert.strictEqual(
			$revealedIp.children().attr( 'title' ),
			expectedTitle,
			'Tooltip for revealed IP'
		);
		done();
	} );
}

QUnit.test( 'Test makeButton on button click for failed request', ( assert ) => {
	performMakeButtonRequestTest( assert, 500, '', '(checkuser-tempaccount-reveal-ip-error)' );
} );

QUnit.test( 'Test makeButton on button click for successful request but missing data', ( assert ) => {
	performMakeButtonRequestTest(
		assert,
		200,
		JSON.stringify( {
			[ tempName1 ]: {
				revIps: {
					// The API returns the revIds as strings.
					// null indicates the IP is missing from the DB (i.e. that
					// it was never there in the first place).
					// eslint-disable-next-line quote-props
					'1': null
				},
				logIps: null,
				lastUsedIp: null
			},
			autoReveal: false
		} ),
		'(checkuser-tempaccount-reveal-ip-missing)',
		undefined
	);
} );

QUnit.test( 'Test makeButton on button click for successful request but expired data', ( assert ) => {
	performMakeButtonRequestTest(
		assert,
		200,
		JSON.stringify( {
			[ tempName1 ]: {
				revIps: {
					// The API returns the revIds as strings.
					// Having the key missing here indicates that the data has
					// expired.
				},
				logIps: null,
				lastUsedIp: null
			},
			autoReveal: false
		} ),
		'(checkuser-tempaccount-reveal-ip-expired)',
		undefined
	);
} );

QUnit.test( 'Test makeButton on button click for successful request with data', ( assert ) => {
	performMakeButtonRequestTest(
		assert,
		200,
		JSON.stringify( {
			[ tempName1 ]: {
				revIps: {
					// The API returns the revIds as strings
					// eslint-disable-next-line quote-props
					'1': '127.0.0.1'
				},
				logIps: null,
				lastUsedIp: null
			},
			autoReveal: false
		} ),
		'127.0.0.1',
		'(checkuser-tempaccount-reveal-ip-tooltip-revision)'
	);
} );

QUnit.test( 'Test enableAutoReveal replaces buttons with IPs', function ( assert ) {
	const done = assert.async();
	server.respond( ( request ) => {
		const response = {};
		response[ tempName1 ] = { revIps: { 1: '127.0.0.1', 2: '127.0.0.1' }, logIps: null, lastUsedIp: null };
		response[ tempName2 ] = { revIps: { 3: '::1' }, logIps: null, lastUsedIp: null };
		request.respond(
			200,
			{ 'Content-Type': 'application/json' },
			JSON.stringify( response )
		);
	} );

	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );

	// Add some revision lines with temporary account links
	const revisionLines = { 1: tempName1, 2: tempName1, 3: tempName2 };
	Object.entries( revisionLines ).forEach( ( [ revId, username ] ) => {
		const $revisionLine = $( '<div>' ).attr( 'data-mw-revid', revId );
		$qunitFixture.append( $revisionLine );
		const $tempAccountUserLink = makeTempUserLink( username );
		$revisionLine.append( $tempAccountUserLink );
		$revisionLine.append( ipReveal.makeButton(
			username,
			{ targetId: revId, allIds: [ revId ] },
			{},
			{},
			undefined,
			$qunitFixture
		) );
	} );

	// Check that auto-reveal mode is switched on
	const expiry = Math.round( Date.now() / 1000 ) + 3600;
	const utilsMock = this.sandbox.mock( ipRevealUtils );
	utilsMock.expects( 'setAutoRevealStatus' )
		.once()
		.withArgs( expiry )
		.returns( $.Deferred().resolve() );

	// Enable multi-reveal and switch on auto-reveal mode
	ipReveal.enableAutoReveal( expiry, $qunitFixture );

	// Check all IPs are revealed
	waitUntilElementDisappears( '.ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
			0,
			'IP reveal buttons removed'
		);
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip', $qunitFixture ).length,
			3,
			'Revealed IPs added'
		);
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip64-anchor', $qunitFixture ).length,
			1,
			'Revealed IPv6s have /64 links added'
		);

		done();
	} );
} );

QUnit.test( 'Test enableAutoReveal with mixed wikiUrls', function ( assert ) {
	const done = assert.async();
	server.respond( ( request ) => {
		const response = {};
		if ( request.url.includes( 'action=query' ) && request.url.includes( 'meta=tokens' ) ) {
			request.respond(
				200,
				{ 'Content-Type': 'application/json' },
				JSON.stringify( {
					query: { tokens: { csrftoken: '+\\' } }
				} )
			);
			return;
		}
		if ( request.url.includes( '/checkuser/v0/batch-temporaryaccount' ) ) {
			if ( request.url.startsWith( 'test1' ) ) {
				response[ tempName1 ] = {
					revIps: { 1: '127.0.0.1' },
					logIps: null,
					lastUsedIp: null,
					autoReveal: true
				};
				response[ tempName2 ] = {
					revIps: { 3: '127.0.0.3' },
					logIps: null,
					lastUsedIp: null,
					autoReveal: true
				};
			} else if ( request.url.startsWith( 'test2' ) ) {
				response[ tempName1 ] = {
					revIps: { 2: '127.0.0.2' },
					logIps: null,
					lastUsedIp: null,
					autoReveal: true
				};
			}
			request.respond(
				200,
				{ 'Content-Type': 'application/json' },
				JSON.stringify( response )
			);
		}
	} );

	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );

	// Add some revision lines with temporary account links with different wikiUrls
	const revisionLines = {
		1: { username: tempName1, wikiUrl: 'test1/$1' },
		2: { username: tempName1, wikiUrl: 'test2/$1' },
		3: { username: tempName2, wikiUrl: 'test1/$1' }
	};
	Object.entries( revisionLines ).forEach( ( [ revId, data ] ) => {
		const $revisionLine = $( '<div>' ).attr( 'data-mw-revid', revId );
		$qunitFixture.append( $revisionLine );
		const $tempAccountUserLink = makeTempUserLink( data.username, data.wikiUrl );
		$revisionLine.append( $tempAccountUserLink );
		$revisionLine.append( ipReveal.makeButton(
			data.username,
			{ targetId: revId, allIds: [ revId ] },
			{},
			{},
			data.wikiUrl,
			$qunitFixture
		) );
	} );

	// Check that auto-reveal mode is switched on
	const expiry = Math.round( Date.now() / 1000 ) + 3600;
	const utilsMock = this.sandbox.mock( ipRevealUtils );
	utilsMock.expects( 'setAutoRevealStatus' )
		.once()
		.withArgs( expiry )
		.returns( $.Deferred().resolve() );

	// Enable multi-reveal and switch on auto-reveal mode
	ipReveal.enableMultiReveal( $qunitFixture );
	ipReveal.enableAutoReveal( expiry, $qunitFixture );

	// Check all IPs are revealed
	waitUntilElementDisappears( '.ext-checkuser-tempaccount-reveal-ip-button' ).then( () => {
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip', $qunitFixture ).length,
			3,
			'Revealed IPs added'
		);

		// Check specific IPs
		assert.strictEqual(
			$qunitFixture.find( '[data-mw-revid="1"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
			'127.0.0.1',
			'IP for revId 1 revealed'
		);
		assert.strictEqual(
			$qunitFixture.find( '[data-mw-revid="2"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
			'127.0.0.2',
			'IP for revId 2 revealed'
		);
		assert.strictEqual(
			$qunitFixture.find( '[data-mw-revid="3"] .ext-checkuser-tempaccount-reveal-ip' ).text(),
			'127.0.0.3',
			'IP for revId 3 revealed'
		);

		done();
	} );
} );

QUnit.test( 'Test disableAutoReveal replaces IPs with buttons', function ( assert ) {
	const done = assert.async();

	// eslint-disable-next-line no-jquery/no-global-selector
	const $qunitFixture = $( '#qunit-fixture' );

	// Add some revision lines with revealed IPs
	const revisionLines = { 1: tempName1, 2: tempName1, 3: tempName2 };
	Object.entries( revisionLines ).forEach( ( [ revId, username ] ) => {
		const $revisionLine = $( '<div>' ).attr( 'data-mw-revid', revId );
		$qunitFixture.append( $revisionLine );
		if ( revId !== '3' ) {
			// Leave one line without an IP, to ensure a button is not added (T404720)
			const $tempAccountUserLink = makeTempUserLink( username );
			const $revealedIp = $( '<span>' ).addClass( 'ext-checkuser-tempaccount-reveal-ip' ).append(
				$( '<a>' ).addClass( 'ext-checkuser-tempaccount-reveal-ip-anchor' )
			);
			$revisionLine.append( $tempAccountUserLink, $revealedIp );
		}
	} );

	// Check that auto-reveal mode is switched off
	const utilsMock = this.sandbox.mock( ipRevealUtils );
	utilsMock.expects( 'setAutoRevealStatus' )
		.once()
		.withArgs()
		.returns( $.Deferred().resolve() );

	// Check that the IPs are replaced with buttons
	ipReveal.disableAutoReveal( $qunitFixture );

	// Check all IPs are hidden
	waitUntilElementDisappears( '.ext-checkuser-tempaccount-reveal-ip' ).then( () => {
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip', $qunitFixture ).length,
			0,
			'Revealed IPs removed'
		);
		assert.strictEqual(
			$( '.ext-checkuser-tempaccount-reveal-ip-button', $qunitFixture ).length,
			2,
			'IP reveal buttons added'
		);

		done();
	} );
} );
