mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-25 08:05:51 +00:00
d01dead5a7
* Eliminates AB.js dependency on sticky header * Code coverage has been raised to 100% * Instead of importing ABTestConfig, these props are now passed into the function along with a token. * WikimediaEvents hook is now fired when experiment is initialized. The experiment should not be initialized if it is not enabled. * Removes several methods (e.g. initAB, getEnabledExperiment) due to the preceeding changes. * Adds `isInSample` and `isInTreatmentBucket` methods so that the client has less work. Treatment buckets now follow a naming convention so that the client can do less work querying if the subject is part of the treatment: * Treatment buckets should have the case-insensitive `treatment` substring somewhere in their name (e.g. 'treatment', 'stickyHeaderTreatment', 'sticky-header-treatment' ) Bug: T302046 Change-Id: I4febec42b4c471b2f2ef02be2e334bd6d2c31eec
232 lines
6.9 KiB
JavaScript
232 lines
6.9 KiB
JavaScript
const AB = require( '../../resources/skins.vector.es6/AB.js' );
|
|
const NAME_OF_EXPERIMENT = 'name-of-experiment';
|
|
const TOKEN = 'token';
|
|
const MW_EXPERIMENT_PARAM = {
|
|
name: NAME_OF_EXPERIMENT,
|
|
enabled: true,
|
|
buckets: {
|
|
unsampled: 0.5,
|
|
control: 0.25,
|
|
treatment: 0.25
|
|
}
|
|
};
|
|
|
|
// eslint-disable-next-line jsdoc/require-returns
|
|
/**
|
|
* @param {Object} props
|
|
*/
|
|
function createInstance( props = {} ) {
|
|
const mergedProps = /** @type {AB.WebABTestProps} */ ( Object.assign( {
|
|
name: NAME_OF_EXPERIMENT,
|
|
buckets: {
|
|
unsampled: {
|
|
samplingRate: 0.5
|
|
},
|
|
control: {
|
|
samplingRate: 0.25
|
|
},
|
|
treatment: {
|
|
samplingRate: 0.25
|
|
}
|
|
},
|
|
token: TOKEN
|
|
}, props ) );
|
|
|
|
return AB( mergedProps );
|
|
}
|
|
|
|
describe( 'AB.js', () => {
|
|
const bucket = 'treatment';
|
|
const getBucketMock = jest.fn().mockReturnValue( bucket );
|
|
mw.experiments.getBucket = getBucketMock;
|
|
|
|
afterEach( () => {
|
|
document.body.removeAttribute( 'class' );
|
|
} );
|
|
|
|
describe( 'initialization when body tag does not contain bucket', () => {
|
|
let /** @type {jest.Mock} */ hookMock;
|
|
|
|
beforeEach( () => {
|
|
hookMock = jest.fn().mockReturnValue( { fire: () => {} } );
|
|
mw.hook = hookMock;
|
|
} );
|
|
|
|
it( 'sends data to WikimediaEvents when the bucket is part of sample (e.g. control)', () => {
|
|
getBucketMock.mockReturnValueOnce( 'control' );
|
|
createInstance();
|
|
expect( hookMock ).toHaveBeenCalled();
|
|
} );
|
|
it( 'sends data to WikimediaEvents when the bucket is part of sample (e.g. treatment)', () => {
|
|
getBucketMock.mockReturnValueOnce( 'treatment' );
|
|
createInstance();
|
|
expect( hookMock ).toHaveBeenCalled();
|
|
} );
|
|
it( 'does not send data to WikimediaEvents when the bucket is unsampled ', () => {
|
|
getBucketMock.mockReturnValueOnce( 'unsampled' );
|
|
createInstance();
|
|
expect( hookMock ).not.toHaveBeenCalled();
|
|
} );
|
|
} );
|
|
|
|
describe( 'initialization when body tag contains bucket', () => {
|
|
let /** @type {jest.Mock} */ hookMock;
|
|
|
|
beforeEach( () => {
|
|
hookMock = jest.fn().mockReturnValue( { fire: () => {} } );
|
|
mw.hook = hookMock;
|
|
} );
|
|
|
|
it( 'sends data to WikimediaEvents when the bucket is part of sample (e.g. control)', () => {
|
|
document.body.classList.add( 'name-of-experiment-control' );
|
|
createInstance();
|
|
expect( hookMock ).toHaveBeenCalled();
|
|
} );
|
|
it( 'sends data to WikimediaEvents when the bucket is part of sample (e.g. treatment)', () => {
|
|
document.body.classList.add( 'name-of-experiment-treatment' );
|
|
createInstance();
|
|
expect( hookMock ).toHaveBeenCalled();
|
|
} );
|
|
it( 'does not send data to WikimediaEvents when the bucket is unsampled ', () => {
|
|
document.body.classList.add( 'name-of-experiment-unsampled' );
|
|
createInstance();
|
|
expect( hookMock ).not.toHaveBeenCalled();
|
|
} );
|
|
} );
|
|
|
|
describe( 'initialization when token is undefined', () => {
|
|
it( 'throws an error', () => {
|
|
expect( () => {
|
|
createInstance( { token: undefined } );
|
|
} ).toThrow( 'Tried to call `getBucket`' );
|
|
} );
|
|
} );
|
|
|
|
describe( 'getBucket when body tag does not contain AB class', () => {
|
|
it( 'calls mw.experiments.getBucket with config data', () => {
|
|
const experiment = createInstance();
|
|
|
|
expect( getBucketMock ).toBeCalledWith( MW_EXPERIMENT_PARAM, TOKEN );
|
|
expect( experiment.getBucket() ).toBe( bucket );
|
|
} );
|
|
} );
|
|
|
|
describe( 'getBucket when body tag contains AB class that is in the sample', () => {
|
|
it( 'returns the bucket on the body tag', () => {
|
|
document.body.classList.add( 'name-of-experiment-control' );
|
|
const experiment = createInstance();
|
|
|
|
expect( getBucketMock ).not.toHaveBeenCalled();
|
|
expect( experiment.getBucket() ).toBe( 'control' );
|
|
} );
|
|
} );
|
|
|
|
describe( 'getBucket when body tag contains AB class that is not in the sample', () => {
|
|
it( 'returns the bucket on the body tag', () => {
|
|
document.body.classList.add( 'name-of-experiment-unsampled' );
|
|
const experiment = createInstance();
|
|
|
|
expect( getBucketMock ).not.toHaveBeenCalled();
|
|
expect( experiment.getBucket() ).toBe( 'unsampled' );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInBucket', () => {
|
|
it( 'compares assigned bucket with passed in bucket', () => {
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInBucket( 'treatment' ) ).toBe( true );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to unsampled bucket (from server)', () => {
|
|
it( 'returns false', () => {
|
|
document.body.classList.add( 'name-of-experiment-unsampled' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( false );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to control bucket (from server)', () => {
|
|
it( 'returns false', () => {
|
|
document.body.classList.add( 'name-of-experiment-control' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( false );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to treatment bucket (from server)', () => {
|
|
it( 'returns true', () => {
|
|
document.body.classList.add( 'name-of-experiment-treatment' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( true );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to unsampled bucket (from client)', () => {
|
|
it( 'returns false', () => {
|
|
getBucketMock.mockReturnValueOnce( 'unsampled' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( false );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to control bucket (from client)', () => {
|
|
it( 'returns false', () => {
|
|
getBucketMock.mockReturnValueOnce( 'control' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( false );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to treatment bucket (from client)', () => {
|
|
it( 'returns true', () => {
|
|
getBucketMock.mockReturnValueOnce( 'treatment' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( true );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInTreatmentBucket when assigned to treatment bucket (is case insensitive)', () => {
|
|
it( 'returns true', () => {
|
|
getBucketMock.mockReturnValueOnce( 'StickyHeaderVisibleTreatment' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInTreatmentBucket() ).toBe( true );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInSample when in unsampled bucket', () => {
|
|
it( 'returns false', () => {
|
|
document.body.classList.add( 'name-of-experiment-unsampled' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInSample() ).toBe( false );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInSample when in control bucket', () => {
|
|
it( 'returns true', () => {
|
|
document.body.classList.add( 'name-of-experiment-control' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInSample() ).toBe( true );
|
|
} );
|
|
} );
|
|
|
|
describe( 'isInSample when in treatment bucket', () => {
|
|
it( 'returns true', () => {
|
|
document.body.classList.add( 'name-of-experiment-treatment' );
|
|
const experiment = createInstance();
|
|
|
|
expect( experiment.isInSample() ).toBe( true );
|
|
} );
|
|
} );
|
|
} );
|