course-renderer
Version:
Manages CA School Courses file system storage and HTML conversion
328 lines (256 loc) • 11.9 kB
JavaScript
;
const assert = require('assert');
const fs = require('fs-extra');
const cheerio = require('cheerio')
const yaml = require('js-yaml')
const md = require('markdown-it')
const vinyl = require('vinyl')
const md2html = require('../dist/tasks/md2html')
const sanitizer = require('../dist/tasks/path-sanitizer')
const generator = require('../dist/tasks/answer-generator')
const summaryParser = require('../dist/tasks/summary-parser')
const caquestion = require('../dist/markdown/caquestion')
const aceEditor = require('../dist/tasks/ace-editor')
const annotation = require('../dist/markdown/annotation')
describe('rendering markdown files', function() {
var fixtures = {}, rendered = {}, ch = {}, parser
before(function() {
var files = {
option: 'option.md',
fence: 'fence.md',
singleChar: 'singleCharOption.md'
}
parser = md().use(caquestion)
for ( var key in files ) {
fixtures[key] = fs.readFileSync(__dirname + '/fixtures/' + files[key]).toString()
rendered[key] = parser.render(fixtures[key])
ch[key] = cheerio.load(rendered[key])
}
})
describe('Converting markdown files to html', function() {
it('should render html', callback => {
const stream = md2html({ "sourcePath": "./test" });
stream.on('data', file => {
assert(/<[a-z][\s\S]*>/i.test(file.contents.toString()));
assert(/\.html$/.test(file.path))
});
stream.on('end', callback);
stream.write(new vinyl({
path: './test/fixture.q.md',
contents: fs.readFileSync('./test/fixture.q.md')
}));
stream.write(new vinyl({
path: './test/fixture.md',
contents: fs.readFileSync('./test/fixture.md')
}));
stream.end();
})
})
describe('post rendering hooks', function() {
const ch = cheerio.load(fs.readFileSync(__dirname + '/post-rendering-fixture.html', 'utf8'))
it('path sanitizer hook', function() {
const source = "/path/to/my/source"
const repo = "myTestRepo"
sanitizer.pathSanitize(ch, repo, source)
ch('.ca-verify-button').each((idx, ele) => {
assert.equal(source, ch(ele).data('question-source'))
assert.equal(repo, ch(ele).data('questionnaire-repo'))
})
})
it('answer-generator hook', function() {
generator.answerGenerator(ch, __dirname + '/..', 'test', ( ) => {
ch('.ca-verify-button').each((idx, ele) => {
assert.notEqual(ch(ele).attr('data-question-id'), '')
assert.equal(answer[ch(ele).data('question-id')], answersMap[idx])
})
})
})
})
describe('ca-question markdown-it plugin', function() {
describe('ca-question annotation parser', function() {
it('parses annotation markups with MS type', function() {
var annotationText = '/// type=MS, answer=[1,2,3]'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'MS')
assert.equal(parsedAnnotation.answer, '1,2,3')
annotationText = '/// type=MS'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
annotationText = '/// type=MS, answer=[1,2,3], readonly=true'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
})
it ('parsed annotation markups with SS type', function() {
var annotationText = '/// type=SS, answer=[2]'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'SS')
assert.equal(parsedAnnotation.answer, '2')
annotationText = '/// type=SS'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
annotationText = '/// type=SS, answer=[2], readonly=true'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
})
it ('parsed annotation markups with TI type', function() {
var annotationText = '/// type=TI, answer=[test]'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'TI')
assert.equal(parsedAnnotation.answer, 'test')
annotationText = '/// type=TI'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
annotationText = '/// type=TI, answer=[test], readonly=true'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
})
it ('parsed annotation markups with CR type', function() {
var annotationText = '/// type=CR, answer=[/path/to/my/proof/file]'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'CR')
assert.equal(parsedAnnotation.answer, '/path/to/my/proof/file')
annotationText = '/// type=CR'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
annotationText = '/// type=CR, answer=[/path/to/my/proof/file], readonly=true'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
})
it ('parsed annotation markups with REPL type', function() {
var annotationText = '/// type=REPL'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'REPL')
annotationText = '/// type=REPL, answer=[hello]'
assert.throws(
() => {
parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
},
Error
)
var annotationText = '/// type=REPL, readonly=true'
var parsedAnnotation = annotation.parseAnnotation(annotationText, 1)
assert.equal(parsedAnnotation.type, 'REPL')
assert.equal(parsedAnnotation.readonly, 'true')
})
it ("It won't accept any other type of question", function() {
var annotationText = '/// type=CQ, answer=[test answer]'
assert.throws(
() => {
parsedAnnotation = annotation.parsedAnnotation(annotationText, 1)
},
Error
)
})
})
describe('ca-question option parser', function() {
it('Can be used to check if a string contains an option marker', function() {
const labels = {
radio: ['SS', 'MS', 'TI', 'CR'],
checkbox:['option 1', 'option 2', 'option 3', 'option 4', 'option 5']
}
ch.option('input[type="checkbox"]').each( (idx, ele) => {
assert.equal(ch.option(ele).val(), idx + 1)
})
ch.option('.checkbox').find('label').each( (idx, ele) => {
assert.equal(ch.option(ele).text().replace(/\s+/, ''), labels.checkbox[idx])
})
ch.option('.checkbox').each( (idx, ele) => {
assert.equal(true, ch.option(ele).hasClass('ca-checkbox') )
})
ch.option('input[type="radio"]').each( (idx, ele) => {
assert.equal(ch.option(ele).val(), idx + 1)
})
ch.option('.radio').find('label').each( (idx, ele ) => {
assert.equal(ch.option(ele).text().replace(/\s+/, ''), labels.radio[idx])
})
ch.option('.radio').each( (idx, ele ) => {
assert.equal(true, ch.option(ele).hasClass('ca-radio'))
})
})
it('Can render single char options', function() {
const label = ['9', '2', '4', '3', '5']
ch.singleChar('input[type="radio"]').each( (idx, ele) => {
assert.equal(ch.singleChar(ele).val(), idx + 1)
})
ch.singleChar('.radio').find('label').each( (idx, ele) => {
assert.equal(ch.singleChar(ele).text().replace(/\s+/, ''), label[idx])
})
ch.singleChar('.radio').each( (idx, ele) => {
assert.equal(true, ch.singleChar(ele).hasClass('ca-radio'))
})
})
})
describe('ca-question fence parser', function() {
it('An annotation with CR type that is followed by a fence markup will be rendered by the cafence', function() {
aceEditor.aceEditor(ch.fence)
assert.equal( true, ch.fence('pre').hasClass('ace-editor') )
assert.equal( true, ch.fence('code').hasClass('language-php') )
assert.equal('$var = 1;\n$var++\n', ch.fence('code').text())
})
})
})
describe('SummaryParser', function() {
var content;
before(function() {
content = fs.readFileSync(__dirname + '/fixtures/SUMMARY.md', 'utf8')
})
it('parses a CA Academy TOC file', function() {
const stream = summaryParser()
let summaryOutput = ''
stream.on('data', (chunk) => {
summaryOutput.concat(chunk.contents.toString())
})
stream.on('end', () => {
const parsed = JSON.parse(summaryOutput)
assert.equal(true, Array.isArray(parsed))
assert.equal(4, parsed.length)
assert.equal('Data Types', parsed[0].text)
assert.equal('content/DataTypes.html', parsed[0].url)
assert.equal(true, Array.isArray(parsed[0].children))
assert.equal(4, parsed[0].children.length)
assert.equal('Strings', parsed[0].children[0].text)
assert.equal('Integers', parsed[0].children[2].text)
})
stream.write(new vinyl({
path: '/test/fixtures/SUMMARY.md',
contents: fs.readFileSync('./test/fixtures/SUMMARY.md')
}))
})
})
after(function() {
fs.removeSync('answer.yml')
fs.removeSync( __dirname + '/answer.yml')
})
})