aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMusikAnimal <musikanimal@gmail.com>2025-03-18 00:46:41 -0400
committerMusikAnimal <musikanimal@gmail.com>2025-03-26 16:09:16 -0400
commit27546c79982d442e92207c89ac65ac3dec287319 (patch)
tree23dabb215bf764eecbf4e8c7aff7e3694bd24709
parentc35fa378279862e2b71ae0e1c1372697c9059f49 (diff)
downloadmediawikicore-27546c79982d442e92207c89ac65ac3dec287319.tar.gz
mediawikicore-27546c79982d442e92207c89ac65ac3dec287319.zip
jest: add coverage report and increase coverage
The Jest config is changed to share coverage reports. Thresholds for failing are set based on roughly the new coverage level, but may be changed as needed. For now, Jest test suites must opt-in to coverage reports, and we're only outputting results to stdout. Bug: T388059 Change-Id: I1bf934c7000a40b506374490422541b3a5c34a22
-rw-r--r--tests/jest/jest.config.js34
-rw-r--r--tests/jest/mediawiki.special.block/NamespacesField.test.js64
-rw-r--r--tests/jest/mediawiki.special.block/SpecialBlock.setup.js2
-rw-r--r--tests/jest/mediawiki.special.block/init.test.js58
4 files changed, 143 insertions, 15 deletions
diff --git a/tests/jest/jest.config.js b/tests/jest/jest.config.js
index bb63bd3aaceb..6ca559fae859 100644
--- a/tests/jest/jest.config.js
+++ b/tests/jest/jest.config.js
@@ -18,33 +18,39 @@ module.exports = {
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
- collectCoverage: false,
+ collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be
// collected
- // collectCoverageFrom: undefined,
+ collectCoverageFrom: [
+ 'resources/src/mediawiki.special.block/**/*.{js,vue}'
+ ],
// The directory where Jest should output its coverage files
- coverageDirectory: 'coverage',
+ // coverageDirectory: 'docs/js/coverage',
// An array of regexp pattern strings used to skip coverage collection
- // coveragePathIgnorePatterns: [
- // '/node_modules/'
- // ],
+ coveragePathIgnorePatterns: [
+ '/node_modules/'
+ ],
// Indicates which provider should be used to instrument code for coverage
- // coverageProvider: "babel",
+ coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
- // coverageReporters: [
- // 'json',
- // 'text',
- // 'lcov',
- // 'clover'
- // ],
+ coverageReporters: [
+ 'text'
+ ],
// An object that configures minimum threshold enforcement for coverage results
- // coverageThreshold: undefined,
+ coverageThreshold: {
+ '**/mediawiki.special.block/**/*': {
+ statements: 60,
+ branches: 40,
+ functions: 40,
+ lines: 63
+ }
+ },
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
diff --git a/tests/jest/mediawiki.special.block/NamespacesField.test.js b/tests/jest/mediawiki.special.block/NamespacesField.test.js
new file mode 100644
index 000000000000..4cb5e50e04db
--- /dev/null
+++ b/tests/jest/mediawiki.special.block/NamespacesField.test.js
@@ -0,0 +1,64 @@
+'use strict';
+
+const { shallowMount } = require( '@vue/test-utils' );
+const { createTestingPinia } = require( '@pinia/testing' );
+const { mockMwConfigGet } = require( './SpecialBlock.setup.js' );
+const NamespacesField = require( '../../../resources/src/mediawiki.special.block/components/NamespacesField.vue' );
+const useBlockStore = require( '../../../resources/src/mediawiki.special.block/stores/block.js' );
+
+describe( 'NamespacesField', () => {
+ it( 'should populate the namespace options from wgFormattedNamespaces', () => {
+ mockMwConfigGet( {
+ wgFormattedNamespaces: {
+ // Don't assume keys are in the correct order.
+ 3: 'User talk',
+ '-1': 'Special',
+ 2: 'User',
+ // Should get replaced with the 'blanknamespace' message.
+ 0: ''
+ }
+ } );
+ const wrapper = shallowMount( NamespacesField, {
+ global: { plugins: [ createTestingPinia( { stubActions: false } ) ] }
+ } );
+ expect( wrapper.vm.menuItems ).toStrictEqual( [
+ { value: 0, label: 'blanknamespace' },
+ { value: 2, label: 'User' },
+ { value: 3, label: 'User talk' }
+ ] );
+ } );
+
+ it( 'should set the initial selections from blockNamespaceRestrictions', () => {
+ mockMwConfigGet( { blockNamespaceRestrictions: '1\n2' } );
+ const wrapper = shallowMount( NamespacesField, {
+ global: { plugins: [ createTestingPinia( { stubActions: false } ) ] }
+ } );
+ expect( wrapper.vm.chips ).toStrictEqual( [
+ {
+ label: 'Talk',
+ value: 1
+ }, {
+ label: 'User',
+ value: 2
+ }
+ ] );
+ } );
+
+ it( 'should update the store with new selections', () => {
+ mockMwConfigGet();
+ const wrapper = shallowMount( NamespacesField, {
+ global: { plugins: [ createTestingPinia( { stubActions: false } ) ] }
+ } );
+ expect( wrapper.vm.chips ).toStrictEqual( [] );
+ wrapper.vm.onUpdateChips( [
+ {
+ label: 'Talk',
+ value: 1
+ }, {
+ label: 'User',
+ value: 2
+ }
+ ] );
+ expect( useBlockStore().namespaces ).toStrictEqual( [ 1, 2 ] );
+ } );
+} );
diff --git a/tests/jest/mediawiki.special.block/SpecialBlock.setup.js b/tests/jest/mediawiki.special.block/SpecialBlock.setup.js
index b4a8a53c52a3..d31c970eec4b 100644
--- a/tests/jest/mediawiki.special.block/SpecialBlock.setup.js
+++ b/tests/jest/mediawiki.special.block/SpecialBlock.setup.js
@@ -54,7 +54,7 @@ function mockMwConfigGet( config = {} ) {
user_talk: 3
},
wgFormattedNamespaces: {
- 0: '(Main)',
+ 0: '',
1: 'Talk',
2: 'User',
3: 'User talk'
diff --git a/tests/jest/mediawiki.special.block/init.test.js b/tests/jest/mediawiki.special.block/init.test.js
new file mode 100644
index 000000000000..2aafe8b26317
--- /dev/null
+++ b/tests/jest/mediawiki.special.block/init.test.js
@@ -0,0 +1,58 @@
+/* global process */
+'use strict';
+
+describe( 'SpecialBlock init.js', () => {
+ let targetInput, Vue;
+
+ beforeEach( () => {
+ document.body.innerHTML = '';
+ const form = document.createElement( 'form' );
+ form.className = 'mw-htmlform';
+ document.body.appendChild( form );
+ targetInput = document.createElement( 'input' );
+ targetInput.id = 'mw-bi-target';
+ form.appendChild( targetInput );
+ mw.Api.prototype.loadMessagesIfMissing = jest.fn().mockResolvedValue( undefined );
+ Vue = require( 'vue' );
+ Vue.createMwApp = jest.fn().mockReturnValue( {
+ use: jest.fn().mockReturnValue( {
+ mount: jest.fn()
+ } )
+ } );
+ } );
+
+ afterEach( () => {
+ jest.resetModules();
+ } );
+
+ it( 'should sync server-provided target input with what will be used in the Vue app', async () => {
+ targetInput.value = 'Example';
+ const mockConfig = {};
+ mw.config.set = ( key, value ) => {
+ mockConfig[ key ] = value;
+ };
+ require( '../../../resources/src/mediawiki.special.block/init.js' );
+ await new Promise( process.nextTick );
+ expect( targetInput.disabled ).toBe( true );
+ expect( mockConfig.blockTargetUserInput ).toBe( 'Example' );
+ } );
+
+ it( 'should give the form the ID mw-block-form', () => {
+ const mockConfig = {};
+ mw.config.set = ( key, value ) => {
+ mockConfig[ key ] = value;
+ };
+ require( '../../../resources/src/mediawiki.special.block/init.js' );
+ expect( document.querySelector( '.mw-htmlform' ).id ).toBe( 'mw-block-form' );
+ expect( mockConfig.blockTargetUserInput ).toBeUndefined();
+ } );
+
+ it( 'should do nothing if there is no mw-htmlform', () => {
+ const htmlForm = document.querySelector( '.mw-htmlform' );
+ htmlForm.className = 'not-mw-htmlform';
+ require( '../../../resources/src/mediawiki.special.block/init.js' );
+ expect( htmlForm.id ).not.toBe( 'mw-block-form' );
+ expect( mw.Api.prototype.loadMessagesIfMissing ).not.toHaveBeenCalled();
+ expect( Vue.createMwApp ).not.toHaveBeenCalled();
+ } );
+} );