Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions public/input-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
'exponent'
);

// Demonstrate toolbar buttons for the chemistry/scientific-notation commands
// added in this branch (\sci, \positiveion, \negativeion). \ion takes
// [sign]{charge}, so the sign-bound shorthands are the natural one-click buttons.
mathField.options.addToolbarButtons([
{ id: 'sci', latex: '\\sci', tooltip: 'scientific notation', icon: 'E' },
{ id: 'positiveion', latex: '\\positiveion', tooltip: 'positive ion', icon: '\\text{ }^{\\text{ }+}' },
{ id: 'negativeion', latex: '\\negativeion', tooltip: 'negative ion', icon: '\\text{ }^{\\text{ }-}' }
]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I said not to add these. In general, input-test.html is set up to have the default options that are used by PG. The exception is the addition of the subscript button to demonstrate the usage of the the addToolBarButtons method, and don't entirely like that I did that. I was planning to eventually add a user interface element to the page to allow dynamically adding and removing buttons from the toolbar. I don't like these buttons that are non-standard being added to this.

const optionsContainer = document.querySelector('.options-container');

for (const option in cfg) {
Expand Down
300 changes: 152 additions & 148 deletions public/unit-test.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,166 +29,170 @@ <h1>Unit Tests</h1>
while (mock.firstChild) mock.firstChild.remove();
});

if (post_xunit_to) {
let xunit = '';
Mocha.process.stdout.write = (line) => (xunit += line);
const runner = mocha.run();

// the following is based on
// https://github.com/saucelabs-sample-scripts/JavaScript/blob/
// 4946c5cf0ab7325dce5562881dba7c28e30989e5/reporting_mocha.js
const failedTests = [];
runner.on('fail', (test, err) => {
const flattenTitles = (test) => {
const titles = [];
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
// Run after window load so the test bundle (mathquill.test.js, included at
// the end of the body) has registered its suites before mocha.run().
window.addEventListener('load', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be an issue that is specific to Google Chrome (and maybe some others), but it does not occur in Firefox, and only happens when the dev server hot reloads. On initial page load the unit tests work as they should, but when the dev server hot reloads they don't. You need to refresh the page to get the unit tests to run again. I have noticed this before, but didn't care enough to look into it and fix it.

Don't use a load event for this though. Instead just reorganize the script tags. End the first script tag on line 31 above. Then move the lines

		<!-- include the library with the tests inlined -->
		<script id="mathquill" src="mathquill.test.js"></script>

after that. Then start a new script tag after that.

if (post_xunit_to) {
let xunit = '';
Mocha.process.stdout.write = (line) => (xunit += line);
const runner = mocha.run();

// the following is based on
// https://github.com/saucelabs-sample-scripts/JavaScript/blob/
// 4946c5cf0ab7325dce5562881dba7c28e30989e5/reporting_mocha.js
const failedTests = [];
runner.on('fail', (test, err) => {
const flattenTitles = (test) => {
const titles = [];
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};

failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
});
});
});

runner.on('end', () => {
fetch(post_xunit_to, { method: 'post', body: xunit }).then(() => {
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
});
} else {
const json = location.search.indexOf('json') >= 0;
const listTests = location.search.indexOf('listTests') >= 0;
const suiteMap = {};
const runner = mocha.run();

runner.on('suite', (suite) => {
const title = xmlEscape(suite.fullTitle());
suiteMap[title] = {
assertions: []
};
if (listTests) {
suiteMap[title].assertions.push({
elapsedTime: 0,
timestamp: 0,
result: true,
message: 'okay'
});
}
});

runner.on('pass', (test) => {
if (!listTests) {
const title = getTestSuiteTitle(test);
const elapsedTime = test.duration / 1000;
const timestamp = Date.now();
suiteMap[title].assertions.push({
elapsedTime: elapsedTime,
timestamp: timestamp,
result: true,
message: xmlEscape(test.title)
runner.on('end', () => {
fetch(post_xunit_to, { method: 'post', body: xunit }).then(() => {
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
}
});

runner.on('fail', (test, err) => {
if (!listTests) {
const title = getTestSuiteTitle(test);
const elapsedTime = test.duration / 1000;
const timestamp = Date.now();
suiteMap[title].assertions.push({
elapsedTime: elapsedTime,
timestamp: timestamp,
result: false,
message: xmlEscape(err.message),
stacktrace: xmlEscape(err.stack),
expected: true,
actual: false
});
}
});

runner.on('end', () => {
const moduleResults = [];
for (const suiteTitle in suiteMap) {
if (suiteMap.hasOwnProperty(suiteTitle)) {
const suiteResults = suiteMap[suiteTitle];
let duration = 0;
for (const assertion of suiteResults.assertions) duration += assertion.elapsedTime;
moduleResults.push({
name: suiteTitle,
assertions: suiteResults.assertions,
time: duration
});
} else {
const json = location.search.indexOf('json') >= 0;
const listTests = location.search.indexOf('listTests') >= 0;
const suiteMap = {};
const runner = mocha.run();

runner.on('suite', (suite) => {
const title = xmlEscape(suite.fullTitle());
suiteMap[title] = {
assertions: []
};
if (listTests) {
suiteMap[title].assertions.push({
elapsedTime: 0,
timestamp: 0,
result: true,
message: 'okay'
});
}
});

runner.on('pass', (test) => {
if (!listTests) {
const title = getTestSuiteTitle(test);
const elapsedTime = test.duration / 1000;
const timestamp = Date.now();
suiteMap[title].assertions.push({
elapsedTime: elapsedTime,
timestamp: timestamp,
result: true,
message: xmlEscape(test.title)
});
}
}
const testResults = {
modules: { mathquill: moduleResults },
passes: runner.stats.passes,
failures: runner.stats.failures,
skips: 0
});

runner.on('fail', (test, err) => {
if (!listTests) {
const title = getTestSuiteTitle(test);
const elapsedTime = test.duration / 1000;
const timestamp = Date.now();
suiteMap[title].assertions.push({
elapsedTime: elapsedTime,
timestamp: timestamp,
result: false,
message: xmlEscape(err.message),
stacktrace: xmlEscape(err.stack),
expected: true,
actual: false
});
}
});

runner.on('end', () => {
const moduleResults = [];
for (const suiteTitle in suiteMap) {
if (suiteMap.hasOwnProperty(suiteTitle)) {
const suiteResults = suiteMap[suiteTitle];
let duration = 0;
for (const assertion of suiteResults.assertions) duration += assertion.elapsedTime;
moduleResults.push({
name: suiteTitle,
assertions: suiteResults.assertions,
time: duration
});
}
}
const testResults = {
modules: { mathquill: moduleResults },
passes: runner.stats.passes,
failures: runner.stats.failures,
skips: 0
};
if (json) window.testResultsString = JSON.stringify(testResults, null, 2);
else window.testResultsString = outputXML(testResults);
});

const getTestSuiteTitle = (test) => xmlEscape(test.parent.fullTitle());

// must escape a few symbols in xml attributes:
// http://stackoverflow.com/questions/866706/
// which-characters-are-invalid-unless-encoded-in-an-xml-attribute
const xmlEscape = (string) => {
if (typeof string !== 'string') return '';
string = string || '';
string = string.replace(/&/g, '&amp;');
string = string.replace(/"/g, '&quot;');
string = string.replace(/</g, '&lt;');
return string;
};
if (json) window.testResultsString = JSON.stringify(testResults, null, 2);
else window.testResultsString = outputXML(testResults);
});

const getTestSuiteTitle = (test) => xmlEscape(test.parent.fullTitle());

// must escape a few symbols in xml attributes:
// http://stackoverflow.com/questions/866706/
// which-characters-are-invalid-unless-encoded-in-an-xml-attribute
const xmlEscape = (string) => {
if (typeof string !== 'string') return '';
string = string || '';
string = string.replace(/&/g, '&amp;');
string = string.replace(/"/g, '&quot;');
string = string.replace(/</g, '&lt;');
return string;
};

const outputXML = (results) => {
const xml = [];
xml.push('<?xml version="1.0"?>');
xml.push('<testsuites>');

for (const moduleName in results.modules) {
const module = results.modules[moduleName];
for (const test of module) {
xml.push(`<testsuite name="${moduleName}.${test.name}" time="${test.time}">`);

for (const assertion of test.assertions) {
const assertionMessage = assertion.message || 'no-assertion-message';
const assertionTime = assertion.elapsedTime;

xml.push(`<testcase name="${assertionMessage}" time="${assertionTime}">`);

if (assertion.result === false) {
xml.push(`<failure message="${assertionMessage}">`);
xml.push(`Expected: ${assertion.expected}\n`);
xml.push(`Actual: ${assertion.actual}\n`);
xml.push(`Stacktrace: ${assertion.stacktrace}`);
xml.push('</failure>');
} else if (assertion.result === undefined) {
xml.push('<skipped />');

const outputXML = (results) => {
const xml = [];
xml.push('<?xml version="1.0"?>');
xml.push('<testsuites>');

for (const moduleName in results.modules) {
const module = results.modules[moduleName];
for (const test of module) {
xml.push(`<testsuite name="${moduleName}.${test.name}" time="${test.time}">`);

for (const assertion of test.assertions) {
const assertionMessage = assertion.message || 'no-assertion-message';
const assertionTime = assertion.elapsedTime;

xml.push(`<testcase name="${assertionMessage}" time="${assertionTime}">`);

if (assertion.result === false) {
xml.push(`<failure message="${assertionMessage}">`);
xml.push(`Expected: ${assertion.expected}\n`);
xml.push(`Actual: ${assertion.actual}\n`);
xml.push(`Stacktrace: ${assertion.stacktrace}`);
xml.push('</failure>');
} else if (assertion.result === undefined) {
xml.push('<skipped />');
}

xml.push('</testcase>');
}

xml.push('</testcase>');
xml.push('</testsuite>');
}

xml.push('</testsuite>');
}
}

return xml.join('\n');
};
}
return xml.join('\n');
};
}
});
</script>
<!-- include the library with the tests inlined -->
<script id="mathquill" src="mathquill.test.js"></script>
Expand Down
Loading
Loading