test: check replaced iff translatable comment
All translatable comments should be replaced, but nothing else.
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
d2ecd03013
commit
793fa8fb52
@ -28,6 +28,17 @@ const COMMENT_TRANSLATIONS = createCommentMap(
|
|||||||
path.resolve(__dirname, './dictionaries')
|
path.resolve(__dirname, './dictionaries')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function getTranslatableComments(dictionariesDir) {
|
||||||
|
const { COMMENTS_TO_TRANSLATE } = require(path.resolve(
|
||||||
|
dictionariesDir,
|
||||||
|
'english',
|
||||||
|
'comments'
|
||||||
|
));
|
||||||
|
return COMMENTS_TO_TRANSLATE.map(({ text }) => text);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getTranslatableComments = getTranslatableComments;
|
||||||
|
|
||||||
function createCommentMap(dictionariesDir) {
|
function createCommentMap(dictionariesDir) {
|
||||||
// get all the languages for which there are dictionaries.
|
// get all the languages for which there are dictionaries.
|
||||||
const languages = fs
|
const languages = fs
|
||||||
|
1989
curriculum/package-lock.json
generated
1989
curriculum/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,8 @@
|
|||||||
"@babel/polyfill": "^7.11.5",
|
"@babel/polyfill": "^7.11.5",
|
||||||
"@babel/preset-env": "^7.11.5",
|
"@babel/preset-env": "^7.11.5",
|
||||||
"@babel/register": "^7.11.5",
|
"@babel/register": "^7.11.5",
|
||||||
|
"acorn": "^8.0.1",
|
||||||
|
"acorn-jsx": "^5.3.1",
|
||||||
"babel-plugin-add-module-exports": "^0.2.1",
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
@ -43,6 +45,7 @@
|
|||||||
"babel-standalone": "^6.26.0",
|
"babel-standalone": "^6.26.0",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
|
"css": "^3.0.0",
|
||||||
"fs-extra": "^6.0.1",
|
"fs-extra": "^6.0.1",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
@ -56,7 +59,13 @@
|
|||||||
"mocha": "8.2.0",
|
"mocha": "8.2.0",
|
||||||
"puppeteer": "^5.3.1",
|
"puppeteer": "^5.3.1",
|
||||||
"readdirp-walk": "^1.7.0",
|
"readdirp-walk": "^1.7.0",
|
||||||
|
"rehype": "^11.0.0",
|
||||||
|
"rework-visit": "^1.0.0",
|
||||||
"rx": "^4.1.0",
|
"rx": "^4.1.0",
|
||||||
"string-similarity": "^4.0.2"
|
"string-similarity": "^4.0.2",
|
||||||
|
"unified": "^9.2.0",
|
||||||
|
"unist-util-visit": "^2.0.3",
|
||||||
|
"validator": "^10.4.0",
|
||||||
|
"vfile": "^4.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,11 @@ const vm = require('vm');
|
|||||||
|
|
||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require('puppeteer');
|
||||||
|
|
||||||
const { getChallengesForLang, getMetaForBlock } = require('../getChallenges');
|
const {
|
||||||
|
getChallengesForLang,
|
||||||
|
getMetaForBlock,
|
||||||
|
getTranslatableComments
|
||||||
|
} = require('../getChallenges');
|
||||||
|
|
||||||
const MongoIds = require('./utils/mongoIds');
|
const MongoIds = require('./utils/mongoIds');
|
||||||
const ChallengeTitles = require('./utils/challengeTitles');
|
const ChallengeTitles = require('./utils/challengeTitles');
|
||||||
@ -54,8 +58,19 @@ const {
|
|||||||
|
|
||||||
const { sortChallenges } = require('./utils/sort-challenges');
|
const { sortChallenges } = require('./utils/sort-challenges');
|
||||||
|
|
||||||
|
const TRANSLATABLE_COMMENTS = getTranslatableComments(
|
||||||
|
path.resolve(__dirname, '..', 'dictionaries')
|
||||||
|
);
|
||||||
|
|
||||||
const testEvaluator = require('../../client/config/test-evaluator').filename;
|
const testEvaluator = require('../../client/config/test-evaluator').filename;
|
||||||
|
|
||||||
|
const commentExtractors = {
|
||||||
|
html: require('./utils/extract-html-comments'),
|
||||||
|
js: require('./utils/extract-js-comments'),
|
||||||
|
jsx: require('./utils/extract-jsx-comments'),
|
||||||
|
css: require('./utils/extract-css-comments')
|
||||||
|
};
|
||||||
|
|
||||||
// rethrow unhandled rejections to make sure the tests exit with -1
|
// rethrow unhandled rejections to make sure the tests exit with -1
|
||||||
process.on('unhandledRejection', err => handleRejection(err));
|
process.on('unhandledRejection', err => handleRejection(err));
|
||||||
|
|
||||||
@ -284,6 +299,72 @@ function populateTestsForLang({ lang, challenges, meta }) {
|
|||||||
challengeTitles.check(title, pathAndTitle);
|
challengeTitles.check(title, pathAndTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Has replaced all the English comments', () => {
|
||||||
|
// special cases are where this process breaks for some reason, but
|
||||||
|
// we have validated that the challenge gets parsed correctly.
|
||||||
|
const specialCases = [
|
||||||
|
'587d7b84367417b2b2512b36',
|
||||||
|
'587d7b84367417b2b2512b37',
|
||||||
|
'587d7db0367417b2b2512b82',
|
||||||
|
'587d7dbe367417b2b2512bb8',
|
||||||
|
'5a24c314108439a4d4036161',
|
||||||
|
'5a24c314108439a4d4036154',
|
||||||
|
'5a94fe0569fb03452672e45c',
|
||||||
|
'5a94fe7769fb03452672e463',
|
||||||
|
'5a24c314108439a4d4036148'
|
||||||
|
];
|
||||||
|
if (specialCases.includes(challenge.id)) return;
|
||||||
|
if (lang === 'english') return;
|
||||||
|
// If no .files, then no seed:
|
||||||
|
if (!challenge.files) return;
|
||||||
|
|
||||||
|
// - None of the translatable comments should appear in the
|
||||||
|
// translations. While this is a crude check, no challenges
|
||||||
|
// currently have the text of a comment elsewhere. If that happens
|
||||||
|
// we can handle that challenge separately.
|
||||||
|
TRANSLATABLE_COMMENTS.forEach(comment => {
|
||||||
|
Object.values(challenge.files).forEach(file => {
|
||||||
|
if (file.contents.includes(comment))
|
||||||
|
throw Error(
|
||||||
|
`English comment '${comment}' should be replaced with its translation`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// - None of the translated comment texts should appear *outside* a
|
||||||
|
// comment
|
||||||
|
Object.values(challenge.files).forEach(file => {
|
||||||
|
let comments = {};
|
||||||
|
|
||||||
|
// We get all the actual comments using the appropriate parsers
|
||||||
|
if (file.ext === 'html') {
|
||||||
|
const commentTypes = ['css', 'html'];
|
||||||
|
for (let type of commentTypes) {
|
||||||
|
const newComments = commentExtractors[type](file.contents);
|
||||||
|
for (const [key, value] of Object.entries(newComments)) {
|
||||||
|
comments[key] = comments[key]
|
||||||
|
? comments[key] + value
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
comments = commentExtractors[file.ext](file.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we compare the number of times a given comment appears
|
||||||
|
// (count) with the number of times the text within it appears
|
||||||
|
// (commentTextCount)
|
||||||
|
for (const [comment, count] of Object.entries(comments)) {
|
||||||
|
const commentTextCount =
|
||||||
|
file.contents.split(comment).length - 1;
|
||||||
|
if (commentTextCount !== count)
|
||||||
|
throw Error(
|
||||||
|
`Translated comment text, ${comment}, should only appear inside comments`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const { challengeType } = challenge;
|
const { challengeType } = challenge;
|
||||||
if (
|
if (
|
||||||
challengeType !== challengeTypes.html &&
|
challengeType !== challengeTypes.html &&
|
||||||
|
9
curriculum/test/utils/comment-to-data.js
Normal file
9
curriculum/test/utils/comment-to-data.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
function commentToData(file, comment) {
|
||||||
|
if (file.data[comment]) {
|
||||||
|
file.data[comment]++;
|
||||||
|
} else {
|
||||||
|
file.data[comment] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.commentToData = commentToData;
|
12
curriculum/test/utils/extract-css-comments.js
Normal file
12
curriculum/test/utils/extract-css-comments.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
var rehype = require('rehype');
|
||||||
|
var vfile = require('vfile');
|
||||||
|
|
||||||
|
var getComments = require('./plugins/get-css-comments');
|
||||||
|
|
||||||
|
const processor = rehype().use(getComments);
|
||||||
|
|
||||||
|
function extractComments(html) {
|
||||||
|
return processor.processSync(vfile(html)).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractComments;
|
32
curriculum/test/utils/extract-css-comments.test.js
Normal file
32
curriculum/test/utils/extract-css-comments.test.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* global expect */
|
||||||
|
const extractCSSComments = require('./extract-css-comments');
|
||||||
|
|
||||||
|
const someHTMLWithCSS = `<body>
|
||||||
|
Some text
|
||||||
|
<style>
|
||||||
|
.body {
|
||||||
|
color: red;
|
||||||
|
/* comment 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
/* comment 1 */
|
||||||
|
background: green;
|
||||||
|
/* comment 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// NOTE: this is a bit finicky. If the css is, say, missing a semi-colon,
|
||||||
|
// nearby comments may be missed.
|
||||||
|
describe('extractCSSComments', () => {
|
||||||
|
it('should return an object with comment keys and count values', () => {
|
||||||
|
const commentCounts = {
|
||||||
|
'comment 1': 2,
|
||||||
|
'comment 2': 1
|
||||||
|
};
|
||||||
|
expect(extractCSSComments(someHTMLWithCSS)).toEqual(commentCounts);
|
||||||
|
});
|
||||||
|
});
|
12
curriculum/test/utils/extract-html-comments.js
Normal file
12
curriculum/test/utils/extract-html-comments.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
var rehype = require('rehype');
|
||||||
|
var vfile = require('vfile');
|
||||||
|
|
||||||
|
var getComments = require('./plugins/get-html-comments');
|
||||||
|
|
||||||
|
const processor = rehype().use(getComments);
|
||||||
|
|
||||||
|
function extractComments(html) {
|
||||||
|
return processor.processSync(vfile(html)).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractComments;
|
22
curriculum/test/utils/extract-html-comments.test.js
Normal file
22
curriculum/test/utils/extract-html-comments.test.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* global expect */
|
||||||
|
const extractHTMLComments = require('./extract-html-comments');
|
||||||
|
|
||||||
|
const someHTML = `<body>
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<!-- a comment -->
|
||||||
|
|
||||||
|
<!-- a comment --><!-- another comment -->
|
||||||
|
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('extractHTMLComments', () => {
|
||||||
|
it('should return an object with comment keys and count values', () => {
|
||||||
|
const commentCounts = {
|
||||||
|
'a comment': 2,
|
||||||
|
'another comment': 1
|
||||||
|
};
|
||||||
|
expect(extractHTMLComments(someHTML)).toEqual(commentCounts);
|
||||||
|
});
|
||||||
|
});
|
17
curriculum/test/utils/extract-js-comments.js
Normal file
17
curriculum/test/utils/extract-js-comments.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const acorn = require('acorn');
|
||||||
|
const { commentToData } = require('./comment-to-data');
|
||||||
|
|
||||||
|
const parser = acorn.Parser;
|
||||||
|
|
||||||
|
function extractComments(js) {
|
||||||
|
let comments = [];
|
||||||
|
const file = { data: {} };
|
||||||
|
parser.parse(js, { onComment: comments, ecmaVersion: 2020 });
|
||||||
|
|
||||||
|
comments
|
||||||
|
.map(({ value }) => value.trim())
|
||||||
|
.forEach(comment => commentToData(file, comment));
|
||||||
|
return file.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractComments;
|
25
curriculum/test/utils/extract-js-comments.test.js
Normal file
25
curriculum/test/utils/extract-js-comments.test.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* global expect */
|
||||||
|
const extractJSComments = require('./extract-js-comments');
|
||||||
|
|
||||||
|
const someJS = `
|
||||||
|
// single line comment
|
||||||
|
|
||||||
|
/*
|
||||||
|
a multiline comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
var x = 'y'; // single line comment
|
||||||
|
|
||||||
|
var y = '// single line comment';
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('extractJSComments', () => {
|
||||||
|
it('should return an object with comment keys and count values', () => {
|
||||||
|
const commentCounts = {
|
||||||
|
'single line comment': 2,
|
||||||
|
'a multiline comment': 1
|
||||||
|
};
|
||||||
|
expect(extractJSComments(someJS)).toEqual(commentCounts);
|
||||||
|
});
|
||||||
|
});
|
18
curriculum/test/utils/extract-jsx-comments.js
Normal file
18
curriculum/test/utils/extract-jsx-comments.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const acorn = require('acorn');
|
||||||
|
const jsx = require('acorn-jsx');
|
||||||
|
const { commentToData } = require('./comment-to-data');
|
||||||
|
|
||||||
|
const parser = acorn.Parser.extend(jsx());
|
||||||
|
|
||||||
|
function extractComments(jsx) {
|
||||||
|
let comments = [];
|
||||||
|
const file = { data: {} };
|
||||||
|
parser.parse(jsx, { onComment: comments, ecmaVersion: 2020 });
|
||||||
|
|
||||||
|
comments
|
||||||
|
.map(({ value }) => value.trim())
|
||||||
|
.forEach(comment => commentToData(file, comment));
|
||||||
|
return file.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractComments;
|
29
curriculum/test/utils/extract-jsx-comments.test.js
Normal file
29
curriculum/test/utils/extract-jsx-comments.test.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/* global expect */
|
||||||
|
const extractJSXComments = require('./extract-jsx-comments');
|
||||||
|
|
||||||
|
const someJSX = `<Link
|
||||||
|
className='btn-invert'
|
||||||
|
to='/username'
|
||||||
|
>
|
||||||
|
Show me my public portfolio
|
||||||
|
{/* JSX comment */}
|
||||||
|
</Link>
|
||||||
|
// single line comment
|
||||||
|
|
||||||
|
{/* JSX comment */}
|
||||||
|
|
||||||
|
/*
|
||||||
|
a multiline comment
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('extractJSXComments', () => {
|
||||||
|
it('should return an object with comment keys and count values', () => {
|
||||||
|
const commentCounts = {
|
||||||
|
'JSX comment': 2,
|
||||||
|
'single line comment': 1,
|
||||||
|
'a multiline comment': 1
|
||||||
|
};
|
||||||
|
expect(extractJSXComments(someJSX)).toEqual(commentCounts);
|
||||||
|
});
|
||||||
|
});
|
29
curriculum/test/utils/plugins/get-css-comments.js
Normal file
29
curriculum/test/utils/plugins/get-css-comments.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const { isEmpty } = require('lodash');
|
||||||
|
const visit = require('unist-util-visit');
|
||||||
|
var css = require('css');
|
||||||
|
var visitCss = require('rework-visit');
|
||||||
|
const { commentToData } = require('../comment-to-data');
|
||||||
|
|
||||||
|
function plugin() {
|
||||||
|
return transformer;
|
||||||
|
|
||||||
|
function transformer(tree, file) {
|
||||||
|
if (isEmpty(file.data)) file.data = {};
|
||||||
|
visit(tree, { type: 'element', tagName: 'style' }, styleVisitor);
|
||||||
|
|
||||||
|
function styleVisitor(node) {
|
||||||
|
visit(node, 'text', cssVisitor);
|
||||||
|
}
|
||||||
|
function cssVisitor(node) {
|
||||||
|
const ast = css.parse(node.value);
|
||||||
|
visitCss(ast.stylesheet, dec => {
|
||||||
|
let comments = dec
|
||||||
|
.filter(({ type }) => type === 'comment')
|
||||||
|
.map(({ comment }) => comment.trim());
|
||||||
|
comments.forEach(comment => commentToData(file, comment));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = plugin;
|
20
curriculum/test/utils/plugins/get-html-comments.js
Normal file
20
curriculum/test/utils/plugins/get-html-comments.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const { isEmpty } = require('lodash');
|
||||||
|
const visit = require('unist-util-visit');
|
||||||
|
const { commentToData } = require('../comment-to-data');
|
||||||
|
|
||||||
|
function plugin() {
|
||||||
|
return transformer;
|
||||||
|
|
||||||
|
function transformer(tree, file) {
|
||||||
|
if (isEmpty(file.data)) {
|
||||||
|
file.data = {};
|
||||||
|
}
|
||||||
|
visit(tree, 'comment', visitor);
|
||||||
|
|
||||||
|
function visitor(node) {
|
||||||
|
commentToData(file, node.value.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = plugin;
|
Reference in New Issue
Block a user