Setup jest unit tests and add basic test cases for AB.js and App.vue
Bug: T300561 Change-Id: Ib7c314b094bd823ae233374f63c9094724d6c06f
This commit is contained in:
parent
44e6289f8d
commit
66359e8fa5
|
@ -3,3 +3,4 @@
|
||||||
/i18n/
|
/i18n/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
/coverage/
|
||||||
|
|
|
@ -22,6 +22,7 @@ sftp-config.json
|
||||||
/docs
|
/docs
|
||||||
/node_modules
|
/node_modules
|
||||||
/vendor
|
/vendor
|
||||||
|
/coverage
|
||||||
|
|
||||||
# Operating systems
|
# Operating systems
|
||||||
## Mac OS X
|
## Mac OS X
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/skinStyles/jquery.ui/
|
/skinStyles/jquery.ui/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
/coverage/
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
// For a detailed explanation regarding each configuration property, visit:
|
||||||
|
// https://jestjs.io/docs/en/configuration.html
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
moduleNameMapper: {
|
||||||
|
},
|
||||||
|
|
||||||
|
// Automatically clear mock calls and instances between every test
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
collectCoverage: true,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files fo
|
||||||
|
// which coverage information should be collected
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'resources/**/*.(js|vue)'
|
||||||
|
],
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
coverageDirectory: 'coverage',
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
coveragePathIgnorePatterns: [
|
||||||
|
'/node_modules/'
|
||||||
|
],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
branches: 0,
|
||||||
|
functions: 0,
|
||||||
|
lines: 0,
|
||||||
|
statements: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
globals: {
|
||||||
|
'vue-jest': {
|
||||||
|
babelConfig: false,
|
||||||
|
hideStyleWarn: true,
|
||||||
|
experimentalCSSCompile: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
moduleFileExtensions: [
|
||||||
|
'js',
|
||||||
|
'json',
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or
|
||||||
|
// set up the testing environment before each test
|
||||||
|
setupFiles: [
|
||||||
|
'./jest.setup.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
transform: {
|
||||||
|
'.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
var mockMediaWiki = require( '@wikimedia/mw-node-qunit/src/mockMediaWiki.js' );
|
||||||
|
global.mw = mockMediaWiki();
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -2,7 +2,8 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bash dev-scripts/setup-storybook.sh && start-storybook --quiet -p 6006 -s resources/skins.vector.styles",
|
"start": "bash dev-scripts/setup-storybook.sh && start-storybook --quiet -p 6006 -s resources/skins.vector.styles",
|
||||||
"test": "npm -s run lint && tsc && npm -s run doc",
|
"test": "npm -s run lint && tsc && npm run test:unit && npm -s run doc",
|
||||||
|
"test:unit": "jest --silent",
|
||||||
"lint": "npm -s run lint:js && npm -s run lint:styles && npm -s run lint:i18n",
|
"lint": "npm -s run lint:js && npm -s run lint:styles && npm -s run lint:i18n",
|
||||||
"lint:fix:js": "npm -s run lint:js -- --fix",
|
"lint:fix:js": "npm -s run lint:js -- --fix",
|
||||||
"lint:fix:styles": "npm -s run lint:styles -- --fix",
|
"lint:fix:styles": "npm -s run lint:styles -- --fix",
|
||||||
|
@ -18,14 +19,18 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.7.7",
|
"@babel/core": "7.7.7",
|
||||||
"@storybook/html": "5.2.8",
|
"@storybook/html": "5.2.8",
|
||||||
|
"@types/jest": "26.0.24",
|
||||||
"@types/jquery": "3.3.33",
|
"@types/jquery": "3.3.33",
|
||||||
"@types/mustache": "4.0.1",
|
"@types/mustache": "4.0.1",
|
||||||
"@types/node-fetch": "2.5.7",
|
"@types/node-fetch": "2.5.7",
|
||||||
|
"@vue/test-utils": "1.1.0",
|
||||||
|
"@wikimedia/mw-node-qunit": "6.3.0",
|
||||||
"@wikimedia/types-wikimedia": "0.2.0",
|
"@wikimedia/types-wikimedia": "0.2.0",
|
||||||
"@wikimedia/wvui": "0.3.0",
|
"@wikimedia/wvui": "0.3.5",
|
||||||
"babel-loader": "8.0.6",
|
"babel-loader": "8.0.6",
|
||||||
"eslint-config-wikimedia": "0.20.0",
|
"eslint-config-wikimedia": "0.20.0",
|
||||||
"grunt-banana-checker": "0.9.0",
|
"grunt-banana-checker": "0.9.0",
|
||||||
|
"jest": "26.4.2",
|
||||||
"jsdoc": "3.6.7",
|
"jsdoc": "3.6.7",
|
||||||
"jsdoc-wmf-theme": "0.0.3",
|
"jsdoc-wmf-theme": "0.0.3",
|
||||||
"less": "3.8.1",
|
"less": "3.8.1",
|
||||||
|
@ -36,6 +41,7 @@
|
||||||
"stylelint-config-wikimedia": "0.11.1",
|
"stylelint-config-wikimedia": "0.11.1",
|
||||||
"svgo": "2.3.1",
|
"svgo": "2.3.1",
|
||||||
"typescript": "4.5.5",
|
"typescript": "4.5.5",
|
||||||
"vue": "2.6.11"
|
"vue": "2.6.11",
|
||||||
|
"vue-jest": "3.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,5 +116,9 @@ function initAB( bucket ) {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isInTestGroup,
|
isInTestGroup,
|
||||||
getEnabledExperiment,
|
getEnabledExperiment,
|
||||||
initAB
|
initAB,
|
||||||
|
test: {
|
||||||
|
getBucketName,
|
||||||
|
getABTestGroupExperimentName
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
{
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": [
|
||||||
|
"../.eslintrc.json"
|
||||||
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2017
|
"ecmaVersion": 2017
|
||||||
},
|
},
|
||||||
|
"rules": {
|
||||||
|
"es/no-object-assign": "off"
|
||||||
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true
|
"node": true,
|
||||||
|
"jest": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
const mockConfig = require( './__mocks__/config.json' );
|
||||||
|
const ABTestConfig = mockConfig.wgVectorWebABTestEnrollment;
|
||||||
|
|
||||||
|
// Mock out virtual config.json file used in AB.js, before importing AB.js
|
||||||
|
jest.mock( '../../resources/skins.vector.es6/config.json', () => {
|
||||||
|
return mockConfig;
|
||||||
|
}, { virtual: true } );
|
||||||
|
const AB = require( '../../resources/skins.vector.es6/AB.js' );
|
||||||
|
|
||||||
|
describe( 'AB.js', () => {
|
||||||
|
const bucket = 'sampled';
|
||||||
|
const userId = '1';
|
||||||
|
const getBucketMock = jest.fn().mockReturnValue( bucket );
|
||||||
|
const toStringMock = jest.fn().mockReturnValue( userId );
|
||||||
|
mw.experiments.getBucket = getBucketMock;
|
||||||
|
// @ts-ignore
|
||||||
|
mw.user.getId = () => ( { toString: toStringMock } );
|
||||||
|
|
||||||
|
const expectedABTestGroupExperimentName = {
|
||||||
|
group: bucket,
|
||||||
|
experimentName: ABTestConfig.name
|
||||||
|
};
|
||||||
|
|
||||||
|
describe( 'getBucketName', () => {
|
||||||
|
it( 'calls mw.experiments.getBucket with config data', () => {
|
||||||
|
expect( AB.test.getBucketName() ).toBe( bucket );
|
||||||
|
expect( getBucketMock ).toBeCalledWith( {
|
||||||
|
name: ABTestConfig.name,
|
||||||
|
enabled: ABTestConfig.enabled,
|
||||||
|
buckets: {
|
||||||
|
unsampled: ABTestConfig.buckets.unsampled.samplingRate,
|
||||||
|
control: ABTestConfig.buckets.control.samplingRate,
|
||||||
|
stickyHeaderDisabled: ABTestConfig.buckets.stickyHeaderDisabled.samplingRate,
|
||||||
|
stickyHeaderEnabled: ABTestConfig.buckets.stickyHeaderEnabled.samplingRate
|
||||||
|
}
|
||||||
|
}, userId );
|
||||||
|
expect( toStringMock ).toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
describe( 'getABTestGroupExperimentName', () => {
|
||||||
|
it( 'returns group and experiment name object', () => {
|
||||||
|
expect( AB.test.getABTestGroupExperimentName() )
|
||||||
|
.toEqual( expectedABTestGroupExperimentName );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
describe( 'getEnabledExperiment', () => {
|
||||||
|
it( 'returns AB config data when enabled', () => {
|
||||||
|
expect( AB.getEnabledExperiment() ).toEqual(
|
||||||
|
Object.assign( {}, expectedABTestGroupExperimentName, ABTestConfig )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
describe( 'initAB(', () => {
|
||||||
|
const hookMock = jest.fn().mockReturnValue( { fire: () => {} } );
|
||||||
|
const isAnonMock = jest.fn();
|
||||||
|
mw.user.isAnon = isAnonMock;
|
||||||
|
mw.hook = hookMock;
|
||||||
|
it( 'sends data to WikimediaEvents when the AB test is enabled ', () => {
|
||||||
|
isAnonMock.mockReturnValueOnce( false );
|
||||||
|
AB.initAB( 'sampled' );
|
||||||
|
expect( hookMock ).toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
it( 'doesnt send data to WikimediaEvents when the user is anon ', () => {
|
||||||
|
isAnonMock.mockReturnValueOnce( true );
|
||||||
|
AB.initAB( 'sampled' );
|
||||||
|
expect( hookMock ).not.toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
it( 'doesnt send data to WikimediaEvents when the bucket is unsampled ', () => {
|
||||||
|
isAnonMock.mockReturnValueOnce( false );
|
||||||
|
AB.initAB( 'unsampled' );
|
||||||
|
expect( hookMock ).not.toHaveBeenCalled();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,35 @@
|
||||||
|
const Vue = require( 'vue' );
|
||||||
|
const VueTestUtils = require( '@vue/test-utils' );
|
||||||
|
const App = require( '../../resources/skins.vector.search/App.vue' );
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
Vue.directive( 'i18n-html', () => {} );
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
id: 'searchform',
|
||||||
|
searchAccessKey: 'f',
|
||||||
|
searchTitle: 'search',
|
||||||
|
searchPlaceholder: 'Search MediaWiki',
|
||||||
|
searchQuery: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const mount = ( /** @type {Object} */ customProps ) => {
|
||||||
|
// @ts-ignore
|
||||||
|
return VueTestUtils.shallowMount( App, {
|
||||||
|
propsData: Object.assign( {}, defaultProps, customProps ),
|
||||||
|
mocks: {
|
||||||
|
$i18n: ( /** @type {string} */ str ) => ( {
|
||||||
|
text: () => str
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
describe( 'App', () => {
|
||||||
|
it( 'renders a typeahead search component', () => {
|
||||||
|
const wrapper = mount();
|
||||||
|
expect(
|
||||||
|
wrapper.element
|
||||||
|
).toMatchSnapshot();
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"wgVectorWebABTestEnrollment": {
|
||||||
|
"name": "vector.sticky_header",
|
||||||
|
"enabled": true,
|
||||||
|
"buckets": {
|
||||||
|
"unsampled": {
|
||||||
|
"samplingRate": 0.1
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"samplingRate": 0.3
|
||||||
|
},
|
||||||
|
"stickyHeaderDisabled": {
|
||||||
|
"samplingRate": 0.3
|
||||||
|
},
|
||||||
|
"stickyHeaderEnabled": {
|
||||||
|
"samplingRate": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Instead of mocking wvui, we ensure it matches the wvui-search module
|
||||||
|
// i.e. https://github.com/wikimedia/mediawiki/blob/master/resources/src/wvui/wvui-search.js
|
||||||
|
// @ts-ignore
|
||||||
|
module.exports = require( '@wikimedia/wvui/dist/commonjs2/wvui-search.commonjs2.js' ).default;
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`App renders a typeahead search component 1`] = `
|
||||||
|
<wvui-typeahead-search-stub
|
||||||
|
accesskey="f"
|
||||||
|
aria-label="Search MediaWiki"
|
||||||
|
buttonlabel="searchbutton"
|
||||||
|
client="[object Object]"
|
||||||
|
domain="localhost"
|
||||||
|
formaction=""
|
||||||
|
highlightquery="true"
|
||||||
|
id="searchform"
|
||||||
|
initialinputvalue=""
|
||||||
|
placeholder="Search MediaWiki"
|
||||||
|
searchpagetitle="Special:Search"
|
||||||
|
showdescription="true"
|
||||||
|
showthumbnail="true"
|
||||||
|
suggestionslabel="searchresults"
|
||||||
|
title="search"
|
||||||
|
urlgenerator="[object Object]"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name="title"
|
||||||
|
type="hidden"
|
||||||
|
value="Special:Search"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
name="wprov"
|
||||||
|
type="hidden"
|
||||||
|
value="acrw1"
|
||||||
|
/>
|
||||||
|
<span />
|
||||||
|
</wvui-typeahead-search-stub>
|
||||||
|
`;
|
|
@ -1,5 +1,9 @@
|
||||||
{
|
{
|
||||||
"exclude": [ "docs", "vendor" ],
|
"exclude": [
|
||||||
|
"docs",
|
||||||
|
"vendor",
|
||||||
|
"coverage"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
Loading…
Reference in New Issue