fix tests to work with webpack

run tests through webpack first, then mocha
reorganise webpack config files
This commit is contained in:
Leo McArdle 2021-12-16 14:53:52 +00:00 коммит произвёл Leo McArdle
Родитель 8e66941f01
Коммит 97a7514315
39 изменённых файлов: 1232 добавлений и 1662 удалений

Просмотреть файл

@ -39,7 +39,7 @@ jobs:
command: ./bin/dc_ci.sh run test ./bin/run-unit-tests.sh
- run:
name: Run js tests
command: ./bin/dc_ci.sh run test ./bin/run-mocha-tests.sh
command: ./bin/dc_ci.sh run test npm run webpack:test
- when:
condition:
or:

Просмотреть файл

@ -28,7 +28,7 @@ repos:
hooks:
- id: eslint
args: [--no-eslintrc, --config=webpack/eslintrc.js]
exclude: "webpack/.*"
exclude: "webpack/.*|webpack\\..*\\.js"
additional_dependencies:
- eslint@8.1.0
- eslint-import-resolver-webpack@0.13.2

Просмотреть файл

@ -81,7 +81,7 @@ test: .docker-build
${DC} run web ./bin/run-unit-tests.sh
test-js: .docker-build
${DC} run web ./bin/run-mocha-tests.sh
${DC} run web npm run webpack:test
docs: .docker-build
${DC} run web $(MAKE) -C docs/ clean

Просмотреть файл

@ -1,5 +0,0 @@
#!/bin/bash
set -ex
./node_modules/.bin/mocha --require ./webpack/mocha-require --recursive kitsune/*/static/*/js/tests/* $@

Просмотреть файл

@ -144,7 +144,7 @@ Running JavaScript Tests
To run tests, make sure you have have the NPM dependencies installed, and
then run::
$ bin/run-mocha-tests.sh
$ npm run webpack:test
Writing JavaScript Tests
------------------------
@ -164,7 +164,6 @@ Here are a few tips for writing tests:
* You can use `sinon` to mock out parts of libraries or functions under
test. This is useful for testing AJAX.
* The tests run in a Node.js environment. A browser environment can be
simulated using ``jsdom``. Specifically, ``mocha-jsdom`` is useful to
set up and tear down the simulated environment.
simulated using ``jsdom``.
.. _Mocha: https://mochajs.org/

Просмотреть файл

@ -5,7 +5,7 @@ export default env;
(function($) {
env.addGlobal('_', gettext);
env.addGlobal('_', window.gettext);
env.addGlobal('ngettext', window.ngettext);
env.addFilter('f', function(fmt, obj, named) {
@ -16,7 +16,7 @@ export default env;
obj[keys[i]] = escape(obj[keys[i]]);
}
return interpolate(fmt, obj, named);
return window.interpolate(fmt, obj, named);
});
env.addFilter('urlparams', function(url, params) {

Просмотреть файл

@ -17,8 +17,6 @@ import AAQSystemInfo from "sumo/js/aaq";
// TODO: Figure out how to break out the functionality here into
// testable parts.
(function($) {
function init() {
var $body = $('body');
@ -278,7 +276,7 @@ import AAQSystemInfo from "sumo/js/aaq";
/*
* Links all crash IDs found in the passed HTML container elements
*/
function linkCrashIds(container) {
export function linkCrashIds(container) {
if (!container) {
return;
}
@ -325,5 +323,3 @@ import AAQSystemInfo from "sumo/js/aaq";
}
$(document).ready(init);
})(jQuery);

Просмотреть файл

@ -345,9 +345,9 @@ ShowFor.prototype.initShowFuncs = function() {
ShowFor.prototype.showAndHide = function() {
this.$container.find('.for').each(function(i, elem) {
var $elem = $(elem);
var showFunc = $elem.data('show-func');
if (showFunc) {
$elem.toggle(showFunc());
var showFuncVal = $elem.data('show-func')();
if (showFuncVal !== undefined) {
$elem.toggle(showFuncVal);
} else {
$elem.show();
}

Просмотреть файл

@ -6,8 +6,6 @@ import _keys from "underscore/modules/keys";
* A tag filtering form.
*/
(function($) {
function init($container) {
var $form = $container ? $container.find('form') : $('#tag-filter form'),
$tags = $form.find('input[type="text"]'), $btn = $form.find('input[type="submit"], button'),
@ -90,9 +88,8 @@ import _keys from "underscore/modules/keys";
const TagsFilter = {
init: init
};
export default TagsFilter;
$(document).ready(function() {
TagsFilter.init();
});
})(jQuery);

Просмотреть файл

@ -1,28 +1,13 @@
import React from 'react';
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {expect} from 'chai';
import sinon from 'sinon';
import mochaGettext from './fixtures/mochaGettext.js';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import AjaxPreview from "sumo/js/ajaxpreview";
describe('ajax preview', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
mochaGettext();
/* globals window, $, k */
var fakeServer;
describe('events', () => {
beforeEach(() => {
rerequire('../ajaxpreview.js');
rerequire('../libs/jquery.lazyload.js');
sinon.stub($, 'ajax').yieldsTo('success', '<p>The content to preview.</p>');

Просмотреть файл

@ -1,25 +1,12 @@
import React from 'react';
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {expect} from 'chai';
import sinon from 'sinon';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import AjaxVote from "sumo/js/ajaxvote";
describe('ajaxvote', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
/* globals window, document, $, k */
describe('helpful vote', () => {
let fakeServer;
beforeEach(() => {
rerequire('../ajaxvote.js');
sinon.stub($, 'ajax').yieldsTo('success', {message: 'Thanks for the vote!'});
let sandbox = (
@ -34,6 +21,7 @@ describe('ajaxvote', () => {
afterEach(() => {
$.ajax.restore();
React.unmountComponentAtNode(document.body);
$(document).off('vote');
});
it('should fire an event on a helpful vote', done => {

Просмотреть файл

@ -1,21 +1,8 @@
import {expect} from 'chai';
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import mochaUnderscore from './fixtures/mochaUnderscore.js';
import BrowserDetect from "sumo/js/browserdetect";
describe('BrowserDetect', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaUnderscore();
/* globals window */
let BrowserDetect;
beforeEach(() => {
rerequire('../browserdetect.js');
BrowserDetect = window.BrowserDetect;
});
describe('Fennec versions', () => {
it('should detect Fennec 7', () => {
let ua = 'Mozilla/5.0 (Android; Linux armv7l; rv:7.0.1) Gecko/ Firefox/7.0.1 Fennec/7.0.1';

Просмотреть файл

@ -1,51 +1,37 @@
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {default as chai, expect} from 'chai';
import chaiLint from 'chai-lint';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaUnderscore from './fixtures/mochaUnderscore.js';
import {
getQueryParamsAsDict,
getReferrer,
getSearchQuery,
unquote,
safeString,
safeInterpolate,
} from "sumo/js/main";
chai.use(chaiLint);
chai.use(sinonChai);
describe('k', () => {
mochaJsdom({
useEach: true,
url: 'http://localhost',
document: {
referrer: 'http://google.com/?q=cookies',
referer: 'http://google.com/?q=cookies',
},
});
mochaJquery();
mochaK();
mochaUnderscore();
/* globals document:false, $:false, k:false */
beforeEach(() => {
rerequire('../libs/jquery.placeholder.js');
rerequire('../main.js');
});
describe('getQueryParamsAsDict', () => {
it('should return an empty object for no params', () => {
let url = 'http://example.com';
let params = k.getQueryParamsAsDict(url);
let params = getQueryParamsAsDict(url);
expect(params).to.deep.equal({});
});
it('should parse a query string with one parameter', () => {
let url = 'http://example.com/?test=woot';
let params = k.getQueryParamsAsDict(url);
let params = getQueryParamsAsDict(url);
expect(params).to.deep.equal({test: 'woot'});
});
it('should parse a query string with two paramaters', () => {
let url = 'http://example.com/?x=foo&y=bar';
let params = k.getQueryParamsAsDict(url);
let params = getQueryParamsAsDict(url);
expect(params).to.deep.equal({x: 'foo', y: 'bar'});
});
@ -54,7 +40,7 @@ describe('k', () => {
'ved=0CDEQFjAA&url=http%3A%2F%2Fsupport.mozilla.com%2F&' +
'rct=j&q=firefox%20help&ei=OsBSTpbZBIGtgQfgzv3yBg&' +
'usg=AFQjCNFIV7wgd9Pnr0m3Ofc7r1zVTNK8dw');
let params = k.getQueryParamsAsDict(url);
let params = getQueryParamsAsDict(url);
expect(params).to.deep.equal({
sa: 't',
source: 'web',
@ -70,50 +56,23 @@ describe('k', () => {
});
});
describe('queryParamStringFromDict', () => {
it('should serialize an empty dict into a ?', () => {
let actual = k.queryParamStringFromDict({});
expect(actual).to.equal('?');
});
it('it should serialize an object with a single key', () => {
let actual = k.queryParamStringFromDict({foo: 1});
expect(actual).to.equal('?foo=1');
});
it('should serialize an object with two keys', () => {
let actual = k.queryParamStringFromDict({foo: 1, bar: 2});
expect(actual).to.equal('?foo=1&bar=2');
});
it('should not include null or undefined in the output', () => {
let actual = k.queryParamStringFromDict({foo: undefined, bar: 2, baz: null});
expect(actual).to.equal('?bar=2');
});
it('should serialize an object with three keys', () => {
let actual = k.queryParamStringFromDict({foo: 1, bar: 2, baz: 3});
expect(actual).to.deep.equal('?foo=1&bar=2&baz=3');
});
});
describe('getReferrer', () => {
it('should recognize search referrers', () => {
let params = {as: 's', s: 'cookies'};
let actual = k.getReferrer(params);
let actual = getReferrer(params);
expect(actual).to.equal('search');
});
it('should recognize inproduct referrers', () => {
let params = {as: 'u'};
let actual = k.getReferrer(params);
let actual = getReferrer(params);
expect(actual).to.equal('inproduct');
});
it('should fall back to `document.referrer`', () => {
let referrer = 'http://google.com/?q=cookies';
expect(document.referrer).to.equal(referrer);
expect(k.getReferrer({})).to.equal(referrer);
expect(getReferrer({})).to.equal(referrer);
});
});
@ -121,54 +80,54 @@ describe('k', () => {
it('should return the s query string for local search referrers', () => {
let params = {as: 's', s: 'cookies'};
let referrer = 'search';
expect(k.getSearchQuery(params, referrer)).to.equal('cookies');
expect(getSearchQuery(params, referrer)).to.equal('cookies');
});
it('should return an empty string fro inproduct referrers', () => {
let params = {as: 'u', s: 'wrong'};
let referrer = 'inproduct';
expect(k.getSearchQuery(params, referrer)).to.equal('');
expect(getSearchQuery(params, referrer)).to.equal('');
});
it('should detect external search parameters from google', () => {
let referrer = 'http://google.com/?q=cookies';
expect(k.getSearchQuery({}, referrer)).to.equal('cookies');
expect(getSearchQuery({}, referrer)).to.equal('cookies');
});
});
describe('unquote', () => {
it('should return undefined for undefined input', () => {
expect(k.unquote(undefined)).to.beUndefined();
expect(unquote(undefined)).to.beUndefined();
});
it('should unquote simply quoted strings', () => {
expect(k.unquote('"delete cookies"')).to.equal('delete cookies');
expect(unquote('"delete cookies"')).to.equal('delete cookies');
});
it('should handle escaped quotes', () => {
expect(k.unquote('"\\"delete\\" cookies"')).to.equal('"delete" cookies');
expect(unquote('"\\"delete\\" cookies"')).to.equal('"delete" cookies');
});
it('should handle escaped quotes with no other quotes', () => {
expect(k.unquote('\\"delete\\" cookies')).to.equal('"delete" cookies');
expect(unquote('\\"delete\\" cookies')).to.equal('"delete" cookies');
});
it('should pass strings without quotes through unmodified', () => {
let s = 'cookies';
expect(k.unquote(s)).to.equal(s);
expect(unquote(s)).to.equal(s);
});
});
describe('safeString', () => {
it('should escape html', function() {
let unsafeString = '<a href="foo&\'">';
let safeString = '&lt;a href=&quot;foo&amp;&#39;&quot;&gt;';
expect(k.safeString(unsafeString)).to.equal(safeString);
let expectedString = '&lt;a href=&quot;foo&amp;&#39;&quot;&gt;';
expect(safeString(unsafeString)).to.equal(expectedString);
});
});
describe('safeInterpolate', () => {
/* k.safeInterpolate works by delegating to `interpolate`, a Django
/* safeInterpolate works by delegating to `interpolate`, a Django
* gettext function. These tests mock out interpolate and make sure
* it was called appropriately.
*/
@ -183,7 +142,7 @@ describe('k', () => {
let unsafe = ['<a>', '<script>'];
let safe = ['&lt;a&gt;', '&lt;script&gt;'];
k.safeInterpolate(html, unsafe, false);
safeInterpolate(html, unsafe, false);
expect(interpolateSpy).to.have.callCount(1);
expect(interpolateSpy).to.have.been.calledWithExactly(html, safe, false);
@ -199,7 +158,7 @@ describe('k', () => {
display: '&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;',
name: 'Jo&amp;mdash;hn',
};
k.safeInterpolate(html, unsafe, true);
safeInterpolate(html, unsafe, true);
expect(interpolateSpy).to.have.callCount(1);
expect(interpolateSpy).to.have.been.calledWithExactly(html, safe, true);

Просмотреть файл

@ -1,26 +1,10 @@
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {expect} from 'chai';
import React from 'react';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaGettext from './fixtures/mochaGettext.js';
import mochaMarky from './fixtures/mochaMarky.js';
import { linkCrashIds } from "sumo/js/questions";
describe('k', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
mochaGettext();
mochaMarky();
/* globals window, document, $, k */
describe('linkCrashIds', () => {
beforeEach(() => {
rerequire('../questions.js');
});
afterEach(() => {
React.unmountComponentAtNode(document.body);
});
@ -39,7 +23,7 @@ describe('k', () => {
);
React.render(sandbox, document.body);
k.linkCrashIds($('body'));
linkCrashIds($('body'));
expect($('.crash-report').length).to.equal(1);
});
@ -61,7 +45,7 @@ describe('k', () => {
);
React.render(sandbox, document.body);
k.linkCrashIds($('body'));
linkCrashIds($('body'));
expect($('.crash-report').length).to.equal(5);
});
@ -73,7 +57,7 @@ describe('k', () => {
</section>
);
React.render(sandbox, document.body);
k.linkCrashIds($('body'));
linkCrashIds($('body'));
expect($('.crash-report').length).to.equal(0);
});
@ -87,7 +71,7 @@ describe('k', () => {
);
React.render(sandbox, document.body);
k.linkCrashIds($('body'));
linkCrashIds($('body'));
expect($('.crash-report').length).to.equal(0);
});
});

Просмотреть файл

@ -1,8 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
export default mochaFixtureHelper(({browser='firefox', version=25.0, OS='winxp'}={}) => {
let BrowserDetect = {browser, version, OS};
return {
BrowserDetect: BrowserDetect,
};
});

Просмотреть файл

@ -1,31 +0,0 @@
/**
* Install globals into the jsdom namespace.
* @param {function} mapFunc This function will be called to get the list of
* things to install into the namespace. Should return an object of keys
* to values to install.
*/
export default function(mapFunc) {
return function(options) {
let map;
global.beforeEach(() => {
map = mapFunc(options);
for (let key in map) {
let val = map[key];
global[key] = val;
if (global.window) {
global.window[key] = val;
}
}
});
global.afterEach(() => {
for (let key in map) {
delete global[key];
if (global.window) {
delete global.window[key];
}
}
});
};
}

Просмотреть файл

@ -1,11 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
function fakeGettext(msgid) {
return msgid;
}
export default mochaFixtureHelper(() => {
return {
gettext: fakeGettext,
};
});

Просмотреть файл

@ -1,8 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
export default mochaFixtureHelper(() => {
return {
_gaq: [],
trackEvent: function() {}
};
});

Просмотреть файл

@ -1,10 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
import jQuery from 'jquery';
export default mochaFixtureHelper(() => {
let jq = jQuery(global.window);
return {
$: jq,
jQuery: jq,
};
});

Просмотреть файл

@ -1,8 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
export default mochaFixtureHelper(() => {
let k = global.k || (global.window ? global.window.k : null) || {};
return {
k: k,
};
});

Просмотреть файл

@ -1,9 +0,0 @@
import {rerequire} from 'mocha-jsdom';
import mochaFixtureHelper from './mochaFixtureHelper.js';
export default mochaFixtureHelper(() => {
rerequire('../../markup.js');
return {
Marky: global.window.Marky,
};
});

Просмотреть файл

@ -1,37 +0,0 @@
import path from 'path';
import {rerequire} from 'mocha-jsdom';
import {FileSystemLoader} from 'nunjucks';
/**
* Load and set up nunjucks and the Sumo nunjucks environment for Mocha tests.
*/
export default function() {
global.beforeEach(() => {
let nunjucks = rerequire('nunjucks');
global.nunjucks = nunjucks;
if (global.window) {
global.window.nunjucks = nunjucks;
}
const originalConfigure = nunjucks.configure;
nunjucks.configure = opts => {
opts.watch = false;
return originalConfigure(opts);
};
rerequire('../../nunjucks.js');
global.window.k.nunjucksEnv.loaders = [
new FileSystemLoader('kitsune/sumo/static/sumo/tpl', true, true)
];
global.window.k.nunjucksEnv.initCache();
});
global.afterEach(() => {
delete global.nunjucks;
delete global.nunjucksEnv;
if (global.window) {
delete global.window.nunjucks;
}
});
}

Просмотреть файл

@ -1,8 +0,0 @@
import mochaFixtureHelper from './mochaFixtureHelper.js';
import _ from 'underscore';
export default mochaFixtureHelper(() => {
return {
_: _,
};
});

Просмотреть файл

@ -1,48 +1,23 @@
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {default as chai, expect} from 'chai';
import React from 'react';
import chaiLint from 'chai-lint';
import sinon from 'sinon';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaGoogleAnalytics from './fixtures/mochaGoogleAnalytics.js';
import mochaNunjucks from './fixtures/mochaNunjucks.js';
import mochaGettext from './fixtures/mochaGettext.js';
import "sumo/js/templates/search-results";
import "sumo/js/instant_search";
import CachedXHR from "sumo/js/cached_xhr";
chai.use(chaiLint);
describe('instant search', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
mochaGoogleAnalytics();
mochaGettext();
mochaNunjucks();
/* globals window, document, $ */
describe('', () => {
let $sandbox;
let clock;
let cxhrMock;
beforeEach(() => {
clock = sinon.useFakeTimers();
window.matchMedia = () => {
return {
matches: false,
addListener: () => {}
}
}
global.matchMedia = window.matchMedia;
window.Mzp = {};
window._localStorage = { getItem: () => undefined };
rerequire('../i18n.js');
global.interpolate = global.window.interpolate;
rerequire('../search_utils.js');
rerequire('../instant_search.js');
cxhrMock = sinon.fake();
sinon.replace(CachedXHR.prototype, "request", cxhrMock);
let content = (
<div>
<div id="main-content"/>
@ -58,6 +33,7 @@ describe('instant search', () => {
afterEach(() => {
React.unmountComponentAtNode(document.body);
clock.restore();
sinon.restore();
});
it('shows and hides the main content correctly', () => {
@ -65,27 +41,24 @@ describe('instant search', () => {
expect($('#main-content').css('display')).to.not.equal('none');
$searchInput.val('test');
$searchInput.keyup();
$searchInput.trigger('keyup');
expect($('#main-content').css('display')).to.equal('none');
$searchInput.val('');
$searchInput.keyup();
$searchInput.trigger('keyup');
expect($('#main-content').css('display')).to.not.equal('none');
});
it('shows the search query at the top of the page', () => {
const query = 'search query';
const requestExpectation = cxhrMock.expects('request')
.once()
.withArgs(sinon.match.string, sinon.match(opts => opts.data.q === query));
const $searchInput = $('#search-q');
$searchInput.val(query);
$searchInput.keyup();
$searchInput.trigger('keyup');
clock.tick(200);
// call the callback to actually render things
requestExpectation.firstCall.args[1].success({
cxhrMock.firstCall.args[1].success({
num_results: 0,
q: query,
});
@ -96,15 +69,14 @@ describe('instant search', () => {
it('escapes the search query at the top of the page', () => {
const query = '<';
const requestExpectation = cxhrMock.expects('request');
const $searchInput = $('#search-q');
$searchInput.val(query);
$searchInput.keyup();
$searchInput.trigger('keyup');
clock.tick(200);
// call the callback to actually render things
requestExpectation.firstCall.args[1].success({
cxhrMock.firstCall.args[1].success({
num_results: 0,
q: query,
});

Просмотреть файл

@ -1,31 +1,16 @@
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {default as chai, expect} from 'chai';
import React from 'react';
import chaiLint from 'chai-lint';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaGettext from './fixtures/mochaGettext.js';
import mochaMarky from './fixtures/mochaMarky.js';
import KBox from "sumo/js/kbox.js";
chai.use(chaiLint);
describe('kbox', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
mochaGettext();
mochaMarky();
/* globals window, document, $ */
describe('declarative', () => {
let $kbox, kbox;
beforeEach(() => {
rerequire('../kbox.js');
let sandbox = (
<div id="sandbox">
<div className="kbox"

Просмотреть файл

@ -1,21 +1,10 @@
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {default as chai, expect} from 'chai';
import React from 'react';
import chaiLint from 'chai-lint';
import mochaJquery from './fixtures/mochaJquery.js';
chai.use(chaiLint);
describe('lazyload', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
/* globals document, $ */
beforeEach(() => {
rerequire('../libs/jquery.lazyload.js');
});
it('should load original image', () => {
let img = <img className="lazy" data-original-src="http://example.com/test.jpg"/>;
React.render(img, document.body);

Просмотреть файл

@ -1,11 +1,10 @@
import React from 'react';
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {default as chai, expect} from 'chai';
import chaiLint from 'chai-lint';
import sinon from 'sinon';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaBrowserDetect from './fixtures/mochaBrowserDetect.js';
import BrowserDetect from 'sumo/js/browserdetect';
import ShowFor from "sumo/js/showfor";
chai.use(chaiLint);
@ -15,7 +14,7 @@ chai.use(chaiLint);
* be bound to the passed $sandbox.
*/
function showForNoInit($sandbox) {
let sf = Object.create(window.ShowFor.prototype);
let sf = Object.create(ShowFor.prototype);
sf.$container = $sandbox;
sf.state = {};
return sf;
@ -29,14 +28,9 @@ function unorderedEquals(arr1, arr2) {
describe('ShowFor', () => {
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
/* globals window, document, $ */
let showFor;
beforeEach(() => {
rerequire('../showfor.js');
// Wow. That's a lot of data. Can we make this smaller?
let sandbox = (
<div>
@ -124,6 +118,14 @@ describe('ShowFor', () => {
React.render(sandbox, document.body);
showFor = showForNoInit($('body'));
BrowserDetect.browser = "firefox";
BrowserDetect.version = 25.0;
BrowserDetect.OS = "winxp";
});
afterEach(() => {
BrowserDetect.init();
});
describe('loadData', () => {
@ -145,13 +147,10 @@ describe('ShowFor', () => {
describe('updateUI', () => {
describe('Firefox 26 on Windows XP', () => {
mochaBrowserDetect({
browser: 'fx',
version: 26.0,
OS: 'winxp',
});
beforeEach(() => {
BrowserDetect.browser = "fx"
BrowserDetect.version = 26.0
BrowserDetect.OS = "winxp"
showFor.loadData();
showFor.updateUI();
});
@ -163,13 +162,10 @@ describe('ShowFor', () => {
});
describe('Firefox for Android 23', () => {
mochaBrowserDetect({
browser: 'm',
version: 23.0,
OS: 'android',
});
beforeEach(() => {
BrowserDetect.browser = "m"
BrowserDetect.version = 23.0
BrowserDetect.OS = "android"
showFor.loadData();
showFor.updateUI();
});
@ -182,7 +178,6 @@ describe('ShowFor', () => {
});
describe('updateState', () => {
mochaBrowserDetect();
beforeEach(() => {
showFor.loadData();
@ -242,7 +237,6 @@ describe('ShowFor', () => {
});
describe('initShowFuncs', () => {
mochaBrowserDetect();
beforeEach(() => {
sinon.stub(showFor, 'matchesCriteria');
@ -274,7 +268,6 @@ describe('ShowFor', () => {
});
describe('showAndHide', () => {
mochaBrowserDetect();
beforeEach(() => {
showFor.loadData();
@ -302,7 +295,6 @@ describe('ShowFor', () => {
});
describe('matchesCriteria', () => {
mochaBrowserDetect();
beforeEach(() => {
showFor.loadData();

Просмотреть файл

@ -1,26 +1,11 @@
import React from 'react';
import {default as mochaJsdom, rerequire} from 'mocha-jsdom';
import {expect} from 'chai';
import sinon from 'sinon';
import mochaGettext from './fixtures/mochaGettext.js';
import mochaK from './fixtures/mochaK.js';
import mochaJquery from './fixtures/mochaJquery.js';
import mochaUnderscore from './fixtures/mochaUnderscore.js';
import TagsFilter from "sumo/js/tags.filter";
describe('k', () => {
let form;
mochaJsdom({useEach: true, url: 'http://localhost'});
mochaJquery();
mochaK();
mochaUnderscore();
/* globals window, $, k */
describe('TagsFilter', () => {
beforeEach(() => {
rerequire('../tags.filter.js');
let sandbox = (
<div>
<section className="tag-filter">
@ -41,7 +26,7 @@ describe('k', () => {
);
React.render(sandbox, window.document.body);
k.TagsFilter.init($('body'));
TagsFilter.init($('body'));
// Don't let forms submit
$('form').submit((e) => e.preventDefault());
});

1381
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -32,9 +32,10 @@
"browser-sync:docs": "browser-sync start --no-open --serveStatic \"styleguide/build\" --files \"styleguide/build/**/*\" --port 4000 --reload-delay=300",
"start": "concurrently --raw --kill-others \"npm run webpack:watch\" \"npm run browser-sync\"",
"lint:webpack": "npx eslint --no-eslintrc -c webpack/eslintrc.js kitsune",
"webpack:build": "npx webpack build --mode development",
"webpack:build:prod": "npx webpack build --mode production",
"webpack:watch": "npx webpack watch --mode development"
"webpack:build": "npx webpack build --config webpack.dev.js",
"webpack:build:prod": "npx webpack build --config webpack.prod.js",
"webpack:watch": "npx webpack watch --config webpack.dev.js",
"webpack:test": "npx webpack build --config webpack.test.js && npx mocha --require ./webpack/mocha-require dist/tests.js"
},
"license": "MPL-2.0",
"dependencies": {
@ -46,7 +47,7 @@
"fontawesome": "^4.3.0",
"jquery": "1.11.3",
"jquery-ui": "1.12.1",
"nunjucks": "^1.3.4",
"nunjucks": "^3.2.3",
"react": "0.13.3",
"underscore": "^1.13.1"
},
@ -74,17 +75,17 @@
"eslint-plugin-import": "^2.25.2",
"exports-loader": "^3.0.0",
"expose-loader": "^3.0.0",
"glob": "^7.2.0",
"html-webpack-plugin": "^5.3.2",
"image-minimizer-webpack-plugin": "^2.2.0",
"imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^9.0.0",
"imports-loader": "^3.0.0",
"jsdom": "^19.0.0",
"kss": "^3.0.0-beta.25",
"locutus": "^2.0.15",
"mini-css-extract-plugin": "^1.6.0",
"mocha": "2.3.2",
"mocha-jsdom": "^2.0.0",
"nunjucks": "^1.3.4",
"onchange": "^6.1.0",
"path-parse": "^1.0.7",
"postcss": "^8.3.5",
@ -92,8 +93,9 @@
"postcss-loader": "^6.1.1",
"sass": "^1.23.2",
"sass-loader": "^12.0.0",
"sinon": "1.16.1",
"sinon": "12.0.1",
"sinon-chai": "2.8.0",
"source-map-support": "^0.5.21",
"style-loader": "^2.0.0",
"stylelint": "^11.1.1",
"stylelint-config-recommended-scss": "^3.3.0",
@ -102,6 +104,7 @@
"svgo": "^1.3.2",
"webpack": "^5.38.1",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2"
"webpack-cli": "^4.7.2",
"webpack-merge": "^5.8.0"
}
}

78
webpack.common.js Normal file
Просмотреть файл

@ -0,0 +1,78 @@
const webpack = require("webpack");
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "development",
resolve: {
alias: {
protocol: "@mozilla-protocol/core/protocol",
sumo: path.resolve(__dirname, "kitsune/sumo/static/sumo"),
community: path.resolve(__dirname, "kitsune/community/static/community"),
kpi: path.resolve(__dirname, "kitsune/kpi/static/kpi"),
},
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
{
test: /\.(svg|png|gif|woff2?)$/,
type: "asset/resource",
},
// we copy these libraries from external sources, so define their exports here,
// rather than having to modify them, making updating them more difficult:
exports(
"./kitsune/sumo/static/sumo/js/libs/dnt-helper.js",
"default Mozilla.dntEnabled"
),
exports(
"./kitsune/sumo/static/sumo/js/libs/uitour.js",
"default Mozilla.UITour"
),
],
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
}),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
],
cache: {
type: "filesystem",
},
devtool: "cheap-module-source-map",
output: {
filename: "[name].js",
},
};
function exports(path, exports) {
// export the named variable
return {
test: require.resolve(path),
loader: "exports-loader",
options: {
type: "module",
exports,
},
};
}

Просмотреть файл

@ -1,90 +0,0 @@
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const AssetJsonPlugin = require("./webpack/asset-json-plugin");
const aliases = require("./webpack/aliases");
const entrypoints = require("./webpack/entrypoints");
const entrypointsHtml = require("./webpack/entrypoints-html");
const exportRules = require("./webpack/export-rules");
const assetModuleFilename = "[name].[contenthash][ext]";
module.exports = (env, argv) => {
const dev = argv.mode === "development";
const config = {
resolve: {
alias: aliases,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
{
test: /\.(svg|png|gif|woff2?)$/,
type: "asset/resource",
},
...exportRules,
],
},
entry: entrypoints,
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
}),
new MiniCssExtractPlugin({
filename: dev ? "[name].css" : "[name].[contenthash].css",
}),
...entrypointsHtml,
new CopyPlugin({
patterns: [
{ from: "node_modules/@mozilla-protocol/core/protocol/img/icons/**", to: assetModuleFilename },
{ from: "kitsune/*/static/**/img/**", to: assetModuleFilename },
],
}),
new ImageMinimizerPlugin({
minimizerOptions: {
plugins: [
"optipng",
"svgo",
]
}
}),
new AssetJsonPlugin(),
],
output: {
filename: dev ? "[name].js" : "[name].[contenthash].js",
assetModuleFilename: assetModuleFilename,
},
cache: dev ? { type: "filesystem" } : false,
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
if (dev) {
// eval source maps don't work with our css loaders
config.devtool = "cheap-module-source-map";
}
return config;
};

40
webpack.dev.js Normal file
Просмотреть файл

@ -0,0 +1,40 @@
const { merge } = require("webpack-merge");
const CopyPlugin = require("copy-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const AssetJsonPlugin = require("./webpack/asset-json-plugin");
const common = require("./webpack.common.js");
const entrypoints = require("./webpack/entrypoints");
const entrypointsHtml = require("./webpack/entrypoints-html");
const assetModuleFilename = "[name].[contenthash][ext]";
module.exports = merge(common, {
entry: entrypoints,
plugins: [
...entrypointsHtml,
new CopyPlugin({
patterns: [
{
from: "node_modules/@mozilla-protocol/core/protocol/img/icons/**",
to: assetModuleFilename,
},
{ from: "kitsune/*/static/**/img/**", to: assetModuleFilename },
],
}),
new ImageMinimizerPlugin({
minimizerOptions: {
plugins: ["optipng", "svgo"],
},
}),
new AssetJsonPlugin(),
],
optimization: {
splitChunks: {
chunks: "all",
},
},
output: {
assetModuleFilename: assetModuleFilename,
},
});

24
webpack.prod.js Normal file
Просмотреть файл

@ -0,0 +1,24 @@
const { mergeWithCustomize, unique } = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const dev = require("./webpack.dev.js");
module.exports = mergeWithCustomize({
customizeArray: unique(
"plugins",
["MiniCssExtractPlugin"],
(plugin) => plugin.constructor && plugin.constructor.name
),
})(dev, {
mode: "production",
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
],
cache: false,
devtool: false,
output: {
filename: "[name].[contenthash].js",
},
});

11
webpack.test.js Normal file
Просмотреть файл

@ -0,0 +1,11 @@
const { merge } = require("webpack-merge");
const glob = require("glob");
const common = require("./webpack.common.js");
module.exports = merge(common, {
target: "node",
entry: {
tests: [...glob.sync("./kitsune/*/static/*/js/tests/*.js")],
},
});

Просмотреть файл

@ -1,8 +0,0 @@
const path = require("path");
module.exports = {
protocol: "@mozilla-protocol/core/protocol",
sumo: path.resolve(__dirname, "../kitsune/sumo/static/sumo"),
community: path.resolve(__dirname, "../kitsune/community/static/community"),
kpi: path.resolve(__dirname, "../kitsune/kpi/static/kpi"),
};

Просмотреть файл

@ -3,7 +3,11 @@ module.exports = {
"plugin:import/recommended",
],
"settings": {
"import/resolver": "webpack",
"import/resolver": {
"webpack": {
"config": "./webpack.common.js",
},
},
},
"env": {
"es6": true,

Просмотреть файл

@ -1,24 +0,0 @@
module.exports = [
// we copy these libraries from external sources, so define their exports here,
// rather than having to modify them, making updating them more difficult:
exports(
"../kitsune/sumo/static/sumo/js/libs/dnt-helper.js",
"default Mozilla.dntEnabled"
),
exports(
"../kitsune/sumo/static/sumo/js/libs/uitour.js",
"default Mozilla.UITour"
),
];
function exports(path, exports) {
// export the named variable
return {
test: require.resolve(path),
loader: "exports-loader",
options: {
type: "module",
exports,
},
};
}

Просмотреть файл

@ -1,15 +1,21 @@
require("@babel/register")({
plugins: [
[
"module-resolver",
{
// make babel resolve our webpack aliases in tests
alias: require("./aliases"),
},
],
],
});
require('source-map-support').install();
// make images imports return null, we don't need them in tests
require.extensions[".svg"] = () => null;
require.extensions[".png"] = () => null;
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM("<html></html>", {
url: "https://example.com",
referrer: "http://google.com/?q=cookies",
});
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.sessionStorage = dom.window.sessionStorage;
global.history = dom.window.history;
global.Element = dom.window.Element;
global.matchMedia = () => ({
matches : false,
addListener : () =>{},
removeListener: () =>{},
});
global.jQuery = global.$ = require("jquery");
require("../kitsune/sumo/static/sumo/js/i18n");