diff --git a/resources/skins.vector.es6/AB.js b/resources/skins.vector.es6/AB.js index 5cb2435b..4254accc 100644 --- a/resources/skins.vector.es6/AB.js +++ b/resources/skins.vector.es6/AB.js @@ -3,6 +3,11 @@ const EXCLUDED_BUCKET = 'unsampled'; const TREATMENT_BUCKET_SUBSTRING = 'treatment'; const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment'; +/** + * @typedef {Function} TreatmentBucketFunction + * @param {string} [a] + * @return {boolean} + */ /** * @typedef {Object} WebABTest @@ -10,7 +15,7 @@ const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment'; * @property {function(): string} getBucket * @property {function(string): boolean} isInBucket * @property {function(): boolean} isInSample - * @property {function(): boolean} isInTreatmentBucket + * @property {TreatmentBucketFunction} isInTreatmentBucket */ /** @@ -41,12 +46,15 @@ const WEB_AB_TEST_ENROLLMENT_HOOK = 'mediawiki.web_AB_test_enrollment'; * name: 'nameOfExperiment', * buckets: { * unsampled: { - * samplingRate: 0.5 + * samplingRate: 0.25 * }, * control: { * samplingRate: 0.25 * }, - * treatment: { + * treatment1: { + * samplingRate: 0.25 + * }, + * treatment2: { * samplingRate: 0.25 * } * }, @@ -147,17 +155,18 @@ module.exports = function webABTest( props ) { /** * Whether or not the subject has been bucketed in a treatment bucket as - * defined by the bucket name containing the case-insensitive `treatment` - * substring (e.g. 'treatment', 'sticky-header-treatment' and - * 'stickyHeaderTreatment' are all assumed to be treatment buckets). + * defined by the bucket name containing the case-insensitive 'treatment', + * 'treatment1', or 'treatment2' substring (e.g. 'treatment', 'treatment1', + * 'sticky-header-treatment1' and 'stickyHeaderTreatment2' are all assumed + * to be treatment buckets). * + * @param {string|null} [treatmentBucketName] lowercase name of bucket. * @return {boolean} */ - function isInTreatmentBucket() { + function isInTreatmentBucket( treatmentBucketName = '' ) { const bucket = getBucket(); - // eslint-disable-next-line no-restricted-syntax - return bucket.toLowerCase().includes( TREATMENT_BUCKET_SUBSTRING ); + return bucket.toLowerCase().includes( `${TREATMENT_BUCKET_SUBSTRING}${treatmentBucketName}` ); } /** diff --git a/resources/skins.vector.es6/main.js b/resources/skins.vector.es6/main.js index 1f72d915..717924c7 100644 --- a/resources/skins.vector.es6/main.js +++ b/resources/skins.vector.es6/main.js @@ -8,6 +8,7 @@ const initTableOfContents = require( './tableOfContents.js' ), deferUntilFrame = require( './deferUntilFrame.js' ), ABTestConfig = require( /** @type {string} */ ( './config.json' ) ).wgVectorWebABTestEnrollment || {}, + stickyHeaderEditIconConfig = require( /** @type {string} */ ( './config.json' ) ).wgVectorStickyHeaderEdit || true, TOC_ID = 'mw-panel-toc', TOC_ID_LEGACY = 'toc', BODY_CONTENT_ID = 'bodyContent', @@ -58,7 +59,7 @@ const getHeadingIntersectionHandler = ( changeActiveSection ) => { function initStickyHeaderABTests( abConfig, isStickyHeaderFeatureAllowed, getEnabledExperiment ) { let show = isStickyHeaderFeatureAllowed, stickyHeaderExperiment, - noEditIcons = true; + noEditIcons = stickyHeaderEditIconConfig; // One of the sticky header AB tests is specified in the config const abTestName = abConfig.name, @@ -74,16 +75,21 @@ function initStickyHeaderABTests( abConfig, isStickyHeaderFeatureAllowed, getEna // If eligible, initialize the AB test stickyHeaderExperiment = getEnabledExperiment( abConfig ); - // If running initial AB test, only show sticky header to treatment group. - if ( abTestName === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME ) { + // If running initial or edit AB test, show sticky header to treatment groups + // only. Unsampled and control buckets do not see sticky header. + if ( abTestName === stickyHeader.STICKY_HEADER_EXPERIMENT_NAME || + abTestName === stickyHeader.STICKY_HEADER_EDIT_EXPERIMENT_NAME + ) { show = stickyHeaderExperiment.isInTreatmentBucket(); } - // If running edit-button AB test, show sticky header to all buckets - // and show edit button for treatment group + // If running edit-button AB test, the edit buttons in sticky header are shown + // to second treatment group only. if ( abTestName === stickyHeader.STICKY_HEADER_EDIT_EXPERIMENT_NAME ) { - show = true; - if ( stickyHeaderExperiment.isInTreatmentBucket() ) { + if ( stickyHeaderExperiment.isInTreatmentBucket( '1' ) ) { + noEditIcons = true; + } + if ( stickyHeaderExperiment.isInTreatmentBucket( '2' ) ) { noEditIcons = false; } } diff --git a/tests/jest/skins.vector.es6/main.test.js b/tests/jest/skins.vector.es6/main.test.js index 87f0c992..76a9c399 100644 --- a/tests/jest/skins.vector.es6/main.test.js +++ b/tests/jest/skins.vector.es6/main.test.js @@ -62,7 +62,7 @@ describe( 'main.js', () => { isEnabled: true, isUserInTreatmentBucket: false, expectedResult: { - showStickyHeader: true, + showStickyHeader: false, disableEditIcons: true } }, @@ -78,7 +78,7 @@ describe( 'main.js', () => { { abConfig: STICKY_HEADER_AB, isEnabled: false, // sticky header unavailable - isUserInTreatmentBucket: false, // not in treament bucket + isUserInTreatmentBucket: false, // not in treatment bucket expectedResult: { showStickyHeader: false, disableEditIcons: true @@ -87,7 +87,7 @@ describe( 'main.js', () => { { abConfig: STICKY_HEADER_AB, isEnabled: true, // sticky header available - isUserInTreatmentBucket: false, // not in treament bucket + isUserInTreatmentBucket: false, // not in treatment bucket expectedResult: { showStickyHeader: false, disableEditIcons: true @@ -96,7 +96,7 @@ describe( 'main.js', () => { { abConfig: STICKY_HEADER_AB, isEnabled: false, // sticky header is not available - isUserInTreatmentBucket: true, // but the user is in the treament bucket + isUserInTreatmentBucket: true, // but the user is in the treatment bucket expectedResult: { showStickyHeader: false, disableEditIcons: true @@ -129,27 +129,33 @@ describe( 'main.js', () => { disableEditIcons: false } } - ].forEach( ( { abConfig, isEnabled, isUserInTreatmentBucket, expectedResult } ) => { - document.documentElement.classList.add( 'vector-sticky-header-enabled' ); - const result = test.initStickyHeaderABTests( + ].forEach( + ( { abConfig, - // isStickyHeaderFeatureAllowed isEnabled, - ( experiment ) => ( { - name: experiment.name, - isInBucket: () => true, - isInSample: () => true, - getBucket: () => 'bucket', - isInTreatmentBucket: () => { - return isUserInTreatmentBucket; - } - } ) - ); - expect( result ).toMatchObject( expectedResult ); - // Check that there are no side effects - expect( - document.documentElement.classList.contains( 'vector-sticky-header-enabled' ) - ).toBe( true ); - } ); + isUserInTreatmentBucket, + expectedResult + } ) => { + document.documentElement.classList.add( 'vector-sticky-header-enabled' ); + const result = test.initStickyHeaderABTests( + abConfig, + // isStickyHeaderFeatureAllowed + isEnabled, + ( experiment ) => ( { + name: experiment.name, + isInBucket: () => true, + isInSample: () => true, + getBucket: () => 'bucket', + isInTreatmentBucket: () => { + return isUserInTreatmentBucket; + } + } ) + ); + expect( result ).toMatchObject( expectedResult ); + // Check that there are no side effects + expect( + document.documentElement.classList.contains( 'vector-sticky-header-enabled' ) + ).toBe( true ); + } ); } ); } );