2 module.exports = function ( grunt ) {
3 grunt.loadNpmTasks( 'grunt-banana-checker' );
4 grunt.loadNpmTasks( 'grunt-eslint' );
5 grunt.loadNpmTasks( 'grunt-karma' );
6 grunt.loadNpmTasks( 'grunt-stylelint' );
8 const fs = require( 'fs' );
9 const wgServer = process.env.MW_SERVER;
10 const wgScriptPath = process.env.MW_SCRIPT_PATH;
11 const karmaProxy = {};
13 let qunitURL = wgServer + wgScriptPath + '/index.php?title=Special:JavaScriptTest/qunit/export';
15 // "MediaWiki" for core, or extension/skin name (e.g. "GrowthExperiments")
16 const qunitComponent = grunt.option( 'qunit-component' );
17 const qunitWatch = grunt.option( 'qunit-watch' ) || false;
18 const qunitWatchFiles = [];
19 if ( qunitComponent ) {
20 qunitURL = qunitURL + '&component=' + qunitComponent;
23 if ( !qunitComponent || qunitComponent === 'MediaWiki' ) {
25 qunitWatchFiles.push( 'tests/qunit/**' );
26 qunitWatchFiles.push( 'resources/**' );
28 // one extension or skin
29 const extPath = __dirname + '/extensions/' + qunitComponent + '/extension.json';
30 const skinPath = __dirname + '/skins/' + qunitComponent + '/skin.json';
31 // eslint-disable-next-line security/detect-non-literal-fs-filename
32 if ( fs.existsSync( extPath ) ) {
33 qunitWatchFiles.push( 'extensions/' + qunitComponent + '/extension.json' );
34 qunitWatchFiles.push( 'extensions/' + qunitComponent + '/{modules,resources,tests}/**' );
36 // eslint-disable-next-line security/detect-non-literal-fs-filename
37 if ( fs.existsSync( skinPath ) ) {
38 qunitWatchFiles.push( 'skins/' + qunitComponent + '/skin.json' );
39 qunitWatchFiles.push( 'skins/' + qunitComponent + '/{modules,resources,tests}/**' );
44 karmaProxy[ wgScriptPath ] = {
45 target: wgServer + wgScriptPath,
52 extensions: [ '.js', '.json', '.vue' ],
54 fix: grunt.option( 'fix' )
60 requireLowerCase: false,
61 disallowBlankTranslations: false
63 core: 'languages/i18n/',
64 codex: 'languages/i18n/codex/',
65 exif: 'languages/i18n/exif/',
66 preferences: 'languages/i18n/preferences/',
67 api: 'includes/api/i18n/',
68 rest: 'includes/Rest/i18n/',
69 installer: 'includes/installer/i18n/',
70 paramvalidator: 'includes/libs/ParamValidator/i18n/'
74 reportNeedlessDisables: true
76 resources: 'resources/src/**/*.{css,less,vue}',
77 config: 'mw-config/**/*.css'
81 '.{stylelintrc,eslintrc}.json',
83 '!{extensions,node_modules,skins,vendor}/**'
90 '@wikimedia/karma-firefox-launcher',
95 base: 'ChromeHeadless',
96 // Chrome requires --no-sandbox in Docker/CI.
97 // WMF CI images expose CHROMIUM_FLAGS which sets that.
98 flags: process.env.CHROMIUM_FLAGS ? ( process.env.CHROMIUM_FLAGS || '' ).split( ' ' ) : []
108 }, ...qunitWatchFiles.map( ( file ) => ( {
115 logLevel: ( process.env.ZUUL_PROJECT ? 'DEBUG' : 'INFO' ),
116 frameworks: [ 'qunit' ],
117 // Disable autostart because we load modules asynchronously.
123 reporters: [ 'mocha' ],
124 singleRun: !qunitWatch,
125 autoWatch: qunitWatch,
126 // Some tests in extensions don't yield for more than the default 10s (T89075)
127 browserNoActivityTimeout: 60 * 1000,
128 // Karma requires Same-Origin (or CORS) by default since v1.1.1
129 // for better stacktraces. But we load the first request from wgServer
130 crossOriginAttribute: false
133 browsers: [ 'FirefoxHeadless' ]
136 browsers: [ 'FirefoxHeadless' ]
139 browsers: [ 'ChromeCustom' ]
144 grunt.registerTask( 'assert-mw-env', () => {
146 if ( !process.env.MW_SERVER ) {
147 grunt.log.error( 'Environment variable MW_SERVER must be set.\n' +
148 'Set this like $wgServer, e.g. "http://localhost"'
152 // MW_SCRIPT_PATH= empty string is valid, e.g. for docroot installs
153 // This includes "composer serve" (Quickstart)
154 if ( process.env.MW_SCRIPT_PATH === undefined ) {
155 grunt.log.error( 'Environment variable MW_SCRIPT_PATH must be set.\n' +
156 'Set this like $wgScriptPath, e.g. "/w"' );
162 grunt.registerTask( 'lint', [ 'eslint', 'banana', 'stylelint' ] );
163 grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:firefox' ] );