зеркало из https://github.com/mozilla/kitsune.git
fix tests to work with webpack
run tests through webpack first, then mocha reorganise webpack config files
This commit is contained in:
Родитель
8e66941f01
Коммит
97a7514315
|
@ -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
|
||||
|
|
2
Makefile
2
Makefile
|
@ -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,313 +17,309 @@ 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');
|
||||
|
||||
function init() {
|
||||
var $body = $('body');
|
||||
// if there's an error on page load, focus the field.
|
||||
$('.has-error input, .has-error textarea').first().focus();
|
||||
|
||||
// if there's an error on page load, focus the field.
|
||||
$('.has-error input, .has-error textarea').first().focus();
|
||||
if ($body.is('.new-question')) {
|
||||
initQuestion();
|
||||
|
||||
if ($body.is('.new-question')) {
|
||||
initQuestion();
|
||||
|
||||
if (window.location.search.indexOf('step=aaq-register') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 1 page');
|
||||
} else if (window.location.search.indexOf('step=aaq-question') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 2 page');
|
||||
}
|
||||
if (window.location.search.indexOf('step=aaq-register') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 1 page');
|
||||
} else if (window.location.search.indexOf('step=aaq-question') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 2 page');
|
||||
}
|
||||
|
||||
if ($body.is('.edit-question')) {
|
||||
initQuestion("editing");
|
||||
}
|
||||
|
||||
if ($body.is('.questions')) {
|
||||
initTagFilterToggle();
|
||||
|
||||
$('#flag-filter input[type="checkbox"]').on('click', function() {
|
||||
window.location = $(this).data('url');
|
||||
});
|
||||
|
||||
if (window.location.pathname.indexOf('questions/new/confirm') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 3 confirm page');
|
||||
}
|
||||
}
|
||||
|
||||
if ($body.is('.answers')) {
|
||||
// Put last search query into search box
|
||||
$('#support-search input[name=q]')
|
||||
.val(unquote($.cookie('last_search')));
|
||||
|
||||
function takeQuestion() {
|
||||
if ($(this).val().length > 0) {
|
||||
var $form = $(this).closest('form');
|
||||
var url = $form.data('take-question-url');
|
||||
var csrftoken = $('input[name=csrfmiddlewaretoken]').val();
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
beforeSend: function(xhr, settings) {
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#id_content').on('keyup', _throttle(takeQuestion, 60000));
|
||||
|
||||
$(document).on('click', '#details-edit', function(ev) {
|
||||
ev.preventDefault();
|
||||
$('#question-details').addClass('editing');
|
||||
});
|
||||
|
||||
initHaveThisProblemTooAjax();
|
||||
initHelpfulVote();
|
||||
initCrashIdLinking();
|
||||
initEditDetails();
|
||||
addReferrerAndQueryToVoteForm();
|
||||
initReplyToAnswer();
|
||||
new AjaxPreview($('#preview'));
|
||||
}
|
||||
|
||||
Marky.createSimpleToolbar('.editor-tools', '#reply-content, #id_content', {cannedResponses: !$body.is('.new-question')});
|
||||
|
||||
// product selector page reloading
|
||||
$('#product-selector select').on('change', function() {
|
||||
var val = $(this).val();
|
||||
var queryParams = getQueryParamsAsDict(document.location.toString());
|
||||
|
||||
if (val === '') {
|
||||
delete queryParams.product;
|
||||
} else {
|
||||
queryParams.product = val;
|
||||
}
|
||||
document.location = document.location.pathname + '?' + $.param(queryParams);
|
||||
});
|
||||
|
||||
// sort questions page reloading
|
||||
$('[data-sort-questions]').on('change', function() {
|
||||
document.location = $(this).val()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the new/edit question page/form
|
||||
*/
|
||||
function initQuestion(action) {
|
||||
var $questionForm = $('#question-form');
|
||||
var aaq = new AAQSystemInfo($questionForm);
|
||||
if (action === "editing") {
|
||||
$("#troubleshooting-field").show();
|
||||
if ($body.is('.edit-question')) {
|
||||
initQuestion("editing");
|
||||
}
|
||||
|
||||
if ($body.is('.questions')) {
|
||||
initTagFilterToggle();
|
||||
|
||||
$('#flag-filter input[type="checkbox"]').on('click', function() {
|
||||
window.location = $(this).data('url');
|
||||
});
|
||||
|
||||
if (window.location.pathname.indexOf('questions/new/confirm') > -1) {
|
||||
trackEvent('Ask A Question Flow', 'step 3 confirm page');
|
||||
}
|
||||
}
|
||||
|
||||
if ($body.is('.answers')) {
|
||||
// Put last search query into search box
|
||||
$('#support-search input[name=q]')
|
||||
.val(unquote($.cookie('last_search')));
|
||||
|
||||
function takeQuestion() {
|
||||
if ($(this).val().length > 0) {
|
||||
var $form = $(this).closest('form');
|
||||
var url = $form.data('take-question-url');
|
||||
var csrftoken = $('input[name=csrfmiddlewaretoken]').val();
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: 'POST',
|
||||
beforeSend: function(xhr, settings) {
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#id_content').on('keyup', _throttle(takeQuestion, 60000));
|
||||
|
||||
$(document).on('click', '#details-edit', function(ev) {
|
||||
ev.preventDefault();
|
||||
$('#question-details').addClass('editing');
|
||||
});
|
||||
|
||||
initHaveThisProblemTooAjax();
|
||||
initHelpfulVote();
|
||||
initCrashIdLinking();
|
||||
initEditDetails();
|
||||
addReferrerAndQueryToVoteForm();
|
||||
initReplyToAnswer();
|
||||
new AjaxPreview($('#preview'));
|
||||
}
|
||||
|
||||
Marky.createSimpleToolbar('.editor-tools', '#reply-content, #id_content', {cannedResponses: !$body.is('.new-question')});
|
||||
|
||||
// product selector page reloading
|
||||
$('#product-selector select').on('change', function() {
|
||||
var val = $(this).val();
|
||||
var queryParams = getQueryParamsAsDict(document.location.toString());
|
||||
|
||||
if (val === '') {
|
||||
delete queryParams.product;
|
||||
} else {
|
||||
hideDetails($questionForm, aaq);
|
||||
queryParams.product = val;
|
||||
}
|
||||
document.location = document.location.pathname + '?' + $.param(queryParams);
|
||||
});
|
||||
|
||||
// sort questions page reloading
|
||||
$('[data-sort-questions]').on('change', function() {
|
||||
document.location = $(this).val()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the new/edit question page/form
|
||||
*/
|
||||
function initQuestion(action) {
|
||||
var $questionForm = $('#question-form');
|
||||
var aaq = new AAQSystemInfo($questionForm);
|
||||
if (action === "editing") {
|
||||
$("#troubleshooting-field").show();
|
||||
} else {
|
||||
hideDetails($questionForm, aaq);
|
||||
}
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
return $('#greeting span.user').length > 0;
|
||||
}
|
||||
function isLoggedIn() {
|
||||
return $('#greeting span.user').length > 0;
|
||||
}
|
||||
|
||||
// Handle changes to the details for a question
|
||||
function initEditDetails() {
|
||||
$('#details-product').on('change', function() {
|
||||
var $selected;
|
||||
// Handle changes to the details for a question
|
||||
function initEditDetails() {
|
||||
$('#details-product').on('change', function() {
|
||||
var $selected;
|
||||
|
||||
$(this).children().each(function() {
|
||||
if (this.selected) {
|
||||
$selected = $(this);
|
||||
$(this).children().each(function() {
|
||||
if (this.selected) {
|
||||
$selected = $(this);
|
||||
}
|
||||
});
|
||||
|
||||
$('#details-topic').children().remove();
|
||||
$('#details-submit').prop('disabled', true);
|
||||
|
||||
$.ajax($selected.data('url'), {
|
||||
'dataType': 'json',
|
||||
'success': function(data) {
|
||||
for (var i = 0; i < data.topics.length; i++) {
|
||||
var topic = data.topics[i];
|
||||
var $opt = $('<option />');
|
||||
|
||||
$opt.attr('value', topic.id);
|
||||
$opt.text(topic.title);
|
||||
|
||||
$('#details-topic').append($opt);
|
||||
}
|
||||
});
|
||||
$('#details-submit').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#details-topic').children().remove();
|
||||
$('#details-submit').prop('disabled', true);
|
||||
// Hide the browser/system details for users on FF with js enabled
|
||||
// and are submitting a question for FF on desktop.
|
||||
function hideDetails($form, aaq) {
|
||||
$form.find('ul').addClass('hide-details');
|
||||
$form.find('a.show, a.hide').click(function(ev) {
|
||||
ev.preventDefault();
|
||||
$(this).closest('li')
|
||||
.toggleClass('show')
|
||||
.toggleClass('hide')
|
||||
.closest('ul')
|
||||
.toggleClass('show-details');
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax($selected.data('url'), {
|
||||
'dataType': 'json',
|
||||
'success': function(data) {
|
||||
for (var i = 0; i < data.topics.length; i++) {
|
||||
var topic = data.topics[i];
|
||||
var $opt = $('<option />');
|
||||
/*
|
||||
* Ajaxify any "I have this problem too" forms (may be multiple per page)
|
||||
*/
|
||||
function initHaveThisProblemTooAjax() {
|
||||
var $container = $('#question div.me-too, .question-tools div.me-too');
|
||||
|
||||
$opt.attr('value', topic.id);
|
||||
$opt.text(topic.title);
|
||||
// ajaxify each form individually so the resulting kbox attaches to
|
||||
// the correct DOM element
|
||||
$container.each(function() {
|
||||
initAjaxForm($(this), 'form', '#vote-thanks');
|
||||
});
|
||||
|
||||
$('#details-topic').append($opt);
|
||||
$container.find('input').click(function() {
|
||||
$(this).attr('disabled', 'disabled');
|
||||
});
|
||||
|
||||
// closing or cancelling the kbox on any of the forms should remove
|
||||
// all of them
|
||||
$container.delegate('.kbox-close, .kbox-cancel', 'click', function(ev) {
|
||||
ev.preventDefault();
|
||||
$container.unbind().remove();
|
||||
});
|
||||
}
|
||||
|
||||
function addReferrerAndQueryToVoteForm() {
|
||||
// Add the source/referrer and query terms to the helpful vote form
|
||||
var urlParams = getQueryParamsAsDict(),
|
||||
referrer = getReferrer(urlParams),
|
||||
query = getSearchQuery(urlParams, referrer);
|
||||
$('form.helpful, .me-too form')
|
||||
.append($('<input type="hidden" name="referrer"/>')
|
||||
.attr('value', referrer))
|
||||
.append($('<input type="hidden" name="query"/>')
|
||||
.attr('value', query));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ajaxify the Helpful/Not Helpful form
|
||||
*/
|
||||
function initHelpfulVote() {
|
||||
$('.sumo-l-two-col--sidebar, #document-list, .answer-tools').each(function() {
|
||||
new AjaxVote($(this).find('form.helpful'), { // eslint-disable-line
|
||||
replaceFormWithMessage: true,
|
||||
removeForm: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper
|
||||
function initAjaxForm($container, formSelector, boxSelector, onKboxClose) {
|
||||
$container.delegate(formSelector, 'submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
var $form = $(this);
|
||||
var url = $form.attr('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.html) {
|
||||
if ($(boxSelector).length === 0) {
|
||||
// We don't have a modal set up yet.
|
||||
var kbox = new KBox(response.html, {
|
||||
container: $container,
|
||||
preClose: onKboxClose
|
||||
});
|
||||
kbox.open();
|
||||
} else {
|
||||
$(boxSelector).html($(response.html).children());
|
||||
}
|
||||
$('#details-submit').prop('disabled', false);
|
||||
} else if (response.message) {
|
||||
var html = '<div class="msg"></div>';
|
||||
$(boxSelector)
|
||||
.html(html)
|
||||
.find('.msg').text(response.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Hide the browser/system details for users on FF with js enabled
|
||||
// and are submitting a question for FF on desktop.
|
||||
function hideDetails($form, aaq) {
|
||||
$form.find('ul').addClass('hide-details');
|
||||
$form.find('a.show, a.hide').click(function(ev) {
|
||||
ev.preventDefault();
|
||||
$(this).closest('li')
|
||||
.toggleClass('show')
|
||||
.toggleClass('hide')
|
||||
.closest('ul')
|
||||
.toggleClass('show-details');
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Ajaxify any "I have this problem too" forms (may be multiple per page)
|
||||
*/
|
||||
function initHaveThisProblemTooAjax() {
|
||||
var $container = $('#question div.me-too, .question-tools div.me-too');
|
||||
|
||||
// ajaxify each form individually so the resulting kbox attaches to
|
||||
// the correct DOM element
|
||||
$container.each(function() {
|
||||
initAjaxForm($(this), 'form', '#vote-thanks');
|
||||
});
|
||||
|
||||
$container.find('input').click(function() {
|
||||
$(this).attr('disabled', 'disabled');
|
||||
});
|
||||
|
||||
// closing or cancelling the kbox on any of the forms should remove
|
||||
// all of them
|
||||
$container.delegate('.kbox-close, .kbox-cancel', 'click', function(ev) {
|
||||
ev.preventDefault();
|
||||
$container.unbind().remove();
|
||||
});
|
||||
}
|
||||
|
||||
function addReferrerAndQueryToVoteForm() {
|
||||
// Add the source/referrer and query terms to the helpful vote form
|
||||
var urlParams = getQueryParamsAsDict(),
|
||||
referrer = getReferrer(urlParams),
|
||||
query = getSearchQuery(urlParams, referrer);
|
||||
$('form.helpful, .me-too form')
|
||||
.append($('<input type="hidden" name="referrer"/>')
|
||||
.attr('value', referrer))
|
||||
.append($('<input type="hidden" name="query"/>')
|
||||
.attr('value', query));
|
||||
}
|
||||
|
||||
/*
|
||||
* Ajaxify the Helpful/Not Helpful form
|
||||
*/
|
||||
function initHelpfulVote() {
|
||||
$('.sumo-l-two-col--sidebar, #document-list, .answer-tools').each(function() {
|
||||
new AjaxVote($(this).find('form.helpful'), { // eslint-disable-line
|
||||
replaceFormWithMessage: true,
|
||||
removeForm: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Helper
|
||||
function initAjaxForm($container, formSelector, boxSelector, onKboxClose) {
|
||||
$container.delegate(formSelector, 'submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
var $form = $(this);
|
||||
var url = $form.attr('action');
|
||||
var data = $form.serialize();
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.html) {
|
||||
if ($(boxSelector).length === 0) {
|
||||
// We don't have a modal set up yet.
|
||||
var kbox = new KBox(response.html, {
|
||||
container: $container,
|
||||
preClose: onKboxClose
|
||||
});
|
||||
kbox.open();
|
||||
} else {
|
||||
$(boxSelector).html($(response.html).children());
|
||||
}
|
||||
} else if (response.message) {
|
||||
var html = '<div class="msg"></div>';
|
||||
$(boxSelector)
|
||||
.html(html)
|
||||
.find('.msg').text(response.message);
|
||||
}
|
||||
|
||||
if (!response.ignored) {
|
||||
// Trigger a document event for others to listen for.
|
||||
$(document).trigger('vote', $.extend(data, {url: url}));
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
var message = gettext('There was an error.');
|
||||
alert(message);
|
||||
if (!response.ignored) {
|
||||
// Trigger a document event for others to listen for.
|
||||
$(document).trigger('vote', $.extend(data, {url: url}));
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
error: function() {
|
||||
var message = gettext('There was an error.');
|
||||
alert(message);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function initTagFilterToggle() {
|
||||
$('#toggle-tag-filter').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#tag-filter').slideToggle('fast'); // CSS3: Y U NO TRANSITION TO `height: auto;`?
|
||||
$(this).toggleClass('off');
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Links all crash IDs found in the passed HTML container elements
|
||||
*/
|
||||
export function linkCrashIds(container) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
var crashIDRegex = new RegExp('(bp-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})', 'g');
|
||||
var crashStatsBase = 'https://crash-stats.mozilla.com/report/index/';
|
||||
var helpingWithCrashesArticle = '/kb/helping-crashes';
|
||||
var crashReportContainer =
|
||||
"<span class='crash-report'>" +
|
||||
"<a href='" + crashStatsBase + "$1' target='_blank'>$1</a>" +
|
||||
"<a href='" + helpingWithCrashesArticle + "' target='_blank'>" +
|
||||
"<img src='" + questionmarkIcon + "'></img></a></span>";
|
||||
|
||||
function initTagFilterToggle() {
|
||||
$('#toggle-tag-filter').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('#tag-filter').slideToggle('fast'); // CSS3: Y U NO TRANSITION TO `height: auto;`?
|
||||
$(this).toggleClass('off');
|
||||
});
|
||||
}
|
||||
container.html(container.html().replace(crashIDRegex, crashReportContainer));
|
||||
}
|
||||
|
||||
/*
|
||||
* Links all crash IDs found in the passed HTML container elements
|
||||
*/
|
||||
function linkCrashIds(container) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
var crashIDRegex = new RegExp('(bp-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})', 'g');
|
||||
var crashStatsBase = 'https://crash-stats.mozilla.com/report/index/';
|
||||
var helpingWithCrashesArticle = '/kb/helping-crashes';
|
||||
var crashReportContainer =
|
||||
"<span class='crash-report'>" +
|
||||
"<a href='" + crashStatsBase + "$1' target='_blank'>$1</a>" +
|
||||
"<a href='" + helpingWithCrashesArticle + "' target='_blank'>" +
|
||||
"<img src='" + questionmarkIcon + "'></img></a></span>";
|
||||
/*
|
||||
* Initialize the automatic linking of crash IDs
|
||||
*/
|
||||
function initCrashIdLinking() {
|
||||
var postContents = $('.question .main-content, .answer .main-content, #more-system-details');
|
||||
postContents.each(function() {
|
||||
linkCrashIds($(this));
|
||||
});
|
||||
}
|
||||
|
||||
container.html(container.html().replace(crashIDRegex, crashReportContainer));
|
||||
}
|
||||
function initReplyToAnswer() {
|
||||
$('a.quoted-reply').click(function() {
|
||||
var contentId = $(this).data('content-id'),
|
||||
$content = $('#' + contentId),
|
||||
text = $content.find('.content-raw').text(),
|
||||
user = $content.find('.display-name').text(),
|
||||
reply_text = `''<p>${user} [[#${contentId}|${gettext('said')}]]</p>''\n<blockquote>${text}\n</blockquote>\n\n`,
|
||||
$textarea = $('#id_content'),
|
||||
oldtext = $textarea.val();
|
||||
|
||||
/*
|
||||
* Initialize the automatic linking of crash IDs
|
||||
*/
|
||||
function initCrashIdLinking() {
|
||||
var postContents = $('.question .main-content, .answer .main-content, #more-system-details');
|
||||
postContents.each(function() {
|
||||
linkCrashIds($(this));
|
||||
});
|
||||
}
|
||||
$textarea.val(oldtext + reply_text);
|
||||
|
||||
function initReplyToAnswer() {
|
||||
$('a.quoted-reply').click(function() {
|
||||
var contentId = $(this).data('content-id'),
|
||||
$content = $('#' + contentId),
|
||||
text = $content.find('.content-raw').text(),
|
||||
user = $content.find('.display-name').text(),
|
||||
reply_text = `''<p>${user} [[#${contentId}|${gettext('said')}]]</p>''\n<blockquote>${text}\n</blockquote>\n\n`,
|
||||
$textarea = $('#id_content'),
|
||||
oldtext = $textarea.val();
|
||||
setTimeout(function() {
|
||||
$textarea.focus();
|
||||
}, 10);
|
||||
|
||||
$textarea.val(oldtext + reply_text);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
$textarea.focus();
|
||||
}, 10);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(init);
|
||||
|
||||
})(jQuery);
|
||||
$(document).ready(init);
|
||||
|
|
|
@ -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,93 +6,90 @@ 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'),
|
||||
$hidden = $('<input type="hidden"/>'),
|
||||
vocab = $tags.data('vocabulary'),
|
||||
lowerVocab = {};
|
||||
|
||||
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'),
|
||||
$hidden = $('<input type="hidden"/>'),
|
||||
vocab = $tags.data('vocabulary'),
|
||||
lowerVocab = {};
|
||||
if (!$form.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$form.length) {
|
||||
return;
|
||||
// Create a lower case vocab for case insensitive match.
|
||||
_each(_keys(vocab), function(name) {
|
||||
lowerVocab[name.toLowerCase()] = vocab[name];
|
||||
});
|
||||
|
||||
// Add a hidden field for comma-separated slugs.
|
||||
$hidden.attr('name', $tags.attr('name'))
|
||||
.appendTo($form);
|
||||
$tags.removeAttr('name');
|
||||
|
||||
// Disable button while text input is empty.
|
||||
$btn.attr('disabled', 'disabled');
|
||||
$tags.keyup(function() {
|
||||
if ($tags.val()) {
|
||||
$btn.removeAttr('disabled');
|
||||
} else {
|
||||
$btn.attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// Create a lower case vocab for case insensitive match.
|
||||
_each(_keys(vocab), function(name) {
|
||||
lowerVocab[name.toLowerCase()] = vocab[name];
|
||||
});
|
||||
|
||||
// Add a hidden field for comma-separated slugs.
|
||||
$hidden.attr('name', $tags.attr('name'))
|
||||
.appendTo($form);
|
||||
$tags.removeAttr('name');
|
||||
|
||||
// Disable button while text input is empty.
|
||||
$btn.attr('disabled', 'disabled');
|
||||
$tags.keyup(function() {
|
||||
if ($tags.val()) {
|
||||
$btn.removeAttr('disabled');
|
||||
} else {
|
||||
$btn.attr('disabled', 'disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// Set up autocomplete
|
||||
// Skip if the autocomplete plugin isn't available (unit tests).
|
||||
if ($tags.autocomplete) {
|
||||
$tags.autocomplete({
|
||||
source: _keys(vocab),
|
||||
delay: 0,
|
||||
minLength: 1
|
||||
});
|
||||
}
|
||||
|
||||
// When form is submitted, get the slugs to send over in request.
|
||||
$form.submit(function() {
|
||||
var tagNames = $tags.val(),
|
||||
slugNames = [],
|
||||
currentSlugs = $form.find('input.current-tagged').val(),
|
||||
slugs,
|
||||
invalid = false;
|
||||
|
||||
// For each tag name, find the slug.
|
||||
_each(tagNames.split(','), function(tag) {
|
||||
var trimmed = $.trim(tag),
|
||||
slug = lowerVocab[trimmed.toLowerCase()];
|
||||
if (slug) {
|
||||
slugNames.push(slug);
|
||||
} else if (trimmed) {
|
||||
invalid = true;
|
||||
alert(interpolate(gettext('Invalid tag entered: %s'), [tag]));
|
||||
}
|
||||
});
|
||||
|
||||
// Invalid or no tags? No requests!
|
||||
if (invalid || slugNames.length === 0) {
|
||||
$form.trigger('ajaxComplete');
|
||||
if (!invalid) {
|
||||
alert(gettext('No tags entered.'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
slugs = slugNames.join(',');
|
||||
|
||||
// Prepend any existing filters applied.
|
||||
if (currentSlugs) {
|
||||
slugs = currentSlugs + ',' + slugs;
|
||||
}
|
||||
$hidden.val(slugs);
|
||||
// Set up autocomplete
|
||||
// Skip if the autocomplete plugin isn't available (unit tests).
|
||||
if ($tags.autocomplete) {
|
||||
$tags.autocomplete({
|
||||
source: _keys(vocab),
|
||||
delay: 0,
|
||||
minLength: 1
|
||||
});
|
||||
}
|
||||
|
||||
const TagsFilter = {
|
||||
init: init
|
||||
};
|
||||
// When form is submitted, get the slugs to send over in request.
|
||||
$form.submit(function() {
|
||||
var tagNames = $tags.val(),
|
||||
slugNames = [],
|
||||
currentSlugs = $form.find('input.current-tagged').val(),
|
||||
slugs,
|
||||
invalid = false;
|
||||
|
||||
$(document).ready(function() {
|
||||
TagsFilter.init();
|
||||
// For each tag name, find the slug.
|
||||
_each(tagNames.split(','), function(tag) {
|
||||
var trimmed = $.trim(tag),
|
||||
slug = lowerVocab[trimmed.toLowerCase()];
|
||||
if (slug) {
|
||||
slugNames.push(slug);
|
||||
} else if (trimmed) {
|
||||
invalid = true;
|
||||
alert(interpolate(gettext('Invalid tag entered: %s'), [tag]));
|
||||
}
|
||||
});
|
||||
|
||||
// Invalid or no tags? No requests!
|
||||
if (invalid || slugNames.length === 0) {
|
||||
$form.trigger('ajaxComplete');
|
||||
if (!invalid) {
|
||||
alert(gettext('No tags entered.'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
slugs = slugNames.join(',');
|
||||
|
||||
// Prepend any existing filters applied.
|
||||
if (currentSlugs) {
|
||||
slugs = currentSlugs + ',' + slugs;
|
||||
}
|
||||
$hidden.val(slugs);
|
||||
});
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
const TagsFilter = {
|
||||
init: init
|
||||
};
|
||||
export default TagsFilter;
|
||||
|
||||
$(document).ready(function() {
|
||||
TagsFilter.init();
|
||||
});
|
||||
|
|
|
@ -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 = '<a href="foo&'">';
|
||||
expect(k.safeString(unsafeString)).to.equal(safeString);
|
||||
let expectedString = '<a href="foo&'">';
|
||||
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 = ['<a>', '<script>'];
|
||||
|
||||
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: '<script>alert('xss');</script>',
|
||||
name: 'Jo&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());
|
||||
});
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
19
package.json
19
package.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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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,
|
||||
},
|
||||
});
|
|
@ -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",
|
||||
},
|
||||
});
|
|
@ -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");
|
||||
|
|
Загрузка…
Ссылка в новой задаче