diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..8497d93 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["transform-es2015-destructuring"], + "ignore": ["./node_modules/**/*", "./jsmime.*"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index de4d1f0..0592cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ dist node_modules +/npm-debug.log +/coverage +/jsmime.* \ No newline at end of file diff --git a/.tern-project b/.tern-project new file mode 100644 index 0000000..f381bb3 --- /dev/null +++ b/.tern-project @@ -0,0 +1,4 @@ +{ + "ecmaVersion": 6, + "libs": [] +} \ No newline at end of file diff --git a/README.md b/README.md index a418516..8a0128f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,23 @@ This code depends on the following ES6 features and Web APIs: * btoa, atob (found on global Windows or WorkerScopes) * TextDecoder +Developments +============ +``` +npm install -g mocha # For running mocha tests +npm install -g http-server # Installing the http-server for firefox test +http-server # At the jsmime root directory + +npm install -g gulp # Installing gulp for mocha test with node + +gulp test #Running all the tests +npm run mocha -- test\test_header.js #Used for running specific test case + +gulp bundle # Creating the dist\jsmime.js that could be able used in browser +gulp coverage # Viewing the running result +gulp watch # Watch the file changes & generating the dist\jsmime.js automatically +``` + Versions and API stability ========================== diff --git a/encodings.js b/encodings.js new file mode 100644 index 0000000..a963513 --- /dev/null +++ b/encodings.js @@ -0,0 +1,12 @@ +'use strict' + + +if (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') { + // TODO: GB18030 decode is invalid in text-encoding + module.exports = require('text-encoding') +} else { + module.exports = { + TextDecoder: TextDecoder, + TextEncoder: TextEncoder + } +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..fa9038f --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,111 @@ +'use strict' + +const watchify = require('watchify') +const browserify = require('browserify') +const gulp = require('gulp') +const plumber = require('gulp-plumber') +// const uglify = require('gulp-uglify') +const source = require('vinyl-source-stream') +const buffer = require('vinyl-buffer') +const gutil = require('gulp-util') +const sourcemaps = require('gulp-sourcemaps') +const mocha = require('gulp-mocha') // Unit testing +const rename = require('gulp-rename') +const babelify = require('babelify') +const istanbul = require('gulp-babel-istanbul') +const clean = require('gulp-clean') + +require('./test') + +// Browsersync + Gulp.js +// http://www.browsersync.cn/docs/gulp/ + +function compile (watch) { + let bundler = browserify('./jsmimeMain.js', { + debug: true, + standalone: 'jsmime' + }) + bundler = bundler.transform(babelify) + bundler.on('log', gutil.log) + + function rebundle () { + return bundler.bundle() + .on('error', function (err) { console.error(err); this.emit('end') }) + .pipe(plumber()) + .pipe(source('./jsmimeMain.js')) + .pipe(rename('jsmime.js')) + .pipe(buffer()) + // .pipe(uglify()) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest('./dist')) + .pipe(gulp.dest('./')) + } + + if (watch) { + bundler = watchify(bundler) + bundler.on('update', function () { + console.log('-> bundling...') + rebundle() + }) + } + + return rebundle() +} + +gulp.task('bundle', function () { + return compile(false) +}) + +gulp.task('watch-bundle', function () { + return compile(true) +}) + +function handleError (e) { + console.error(e.toString()) + this.emit('end') +} + +gulp.task('cleanup', () => { + return gulp.src(['./jsmime.*', './dist/*'], {read:false}) + .pipe(clean({force: true})) +}) + +gulp.task('test', () => { + // gulp-mocha needs filepaths so you can't have any plugins before it + return gulp.src(['test/**/test_*.js'], {read: false}) + .pipe( + mocha({ + ui: 'tdd', + reporters : ['xunit-file', 'spec'], + globals: ['define'] + }).on('error', handleError) + ) +}) + +gulp.task('coverage-hook', () => { + return gulp.src(['./*.js', '!jsmime.*']) + // Covering files + .pipe(istanbul()) + .pipe(istanbul.hookRequire()) +}) + +gulp.task('coverage', ['coverage-hook'], () => { + // gulp-mocha needs filepaths so you can't have any plugins before it + return gulp.src(['test/**/test_*.js'], {read: false}) + .pipe( + mocha({ + ui: 'tdd', + reporters : ['xunit-file', 'spec'], + globals: ['define'] + }).on('error', handleError) + ) + // Creating the reports after tests ran + .pipe( + istanbul.writeReports() + ) + // Enforce a coverage of at least 90% + .pipe( + istanbul.enforceThresholds({ thresholds: { global: 10 } }) + ) +}) diff --git a/headeremitter.js b/headeremitter.js index 1612a6c..aab85f8 100644 --- a/headeremitter.js +++ b/headeremitter.js @@ -1,4 +1,3 @@ -define(function(require) { /** * This module implements the code for emitting structured representations of * MIME headers into their encoded forms. The code here is a companion to, @@ -10,6 +9,7 @@ define(function(require) { "use strict"; var mimeutils = require('./mimeutils'); +const { TextEncoder } = require('./encodings'); // Get the default structured encoders and add them to the map var structuredHeaders = require('./structuredHeaders'); @@ -768,12 +768,9 @@ function addStructuredEncoder(header, encoder) { preferredSpellings.set(lowerName, header); } -return Object.freeze({ +module.exports = Object.freeze({ addStructuredEncoder: addStructuredEncoder, emitStructuredHeader: emitStructuredHeader, emitStructuredHeaders: emitStructuredHeaders, makeStreamingEmitter: makeStreamingEmitter }); - -}); - diff --git a/headerparser.js b/headerparser.js index 4bf5c63..6cbd9a3 100644 --- a/headerparser.js +++ b/headerparser.js @@ -1,4 +1,3 @@ -define(function(require) { /** * This file implements the structured decoding of message header fields. It is * part of the same system as found in mimemimeutils.js, and occasionally makes @@ -8,6 +7,7 @@ define(function(require) { "use strict"; var mimeutils = require('./mimeutils'); +const { TextDecoder } = require('./encodings'); /** * This is the API that we ultimately return. @@ -934,7 +934,10 @@ const kKnownTZs = { * above. */ function parseDateHeader(header) { - let tokens = [for (x of getHeaderTokens(header, ",:", {})) x.toString()]; + let tokens = [] + for (let x of getHeaderTokens(header, ",:", {})) { + tokens.push(x.toString()); + } // What does a Date header look like? In practice, most date headers devolve // into Date: [dow ,] dom mon year hh:mm:ss tzoff [(abbrev)], with the day of // week mostly present and the timezone abbreviation mostly absent. @@ -1148,7 +1151,4 @@ headerparser.parseAddressingHeader = parseAddressingHeader; headerparser.parseDateHeader = parseDateHeader; headerparser.parseParameterHeader = parseParameterHeader; headerparser.parseStructuredHeader = parseStructuredHeader; -return Object.freeze(headerparser); - -}); - +module.exports = Object.freeze(headerparser); diff --git a/jsmime.js b/jsmime.js deleted file mode 100644 index e62e6fd..0000000 --- a/jsmime.js +++ /dev/null @@ -1,7 +0,0 @@ -define(function(require) { - return { - MimeParser: require('./mimeparser'), - headerparser: require('./headerparser'), - headeremitter: require('./headeremitter') - } -}); diff --git a/jsmimeDefine.js b/jsmimeDefine.js new file mode 100644 index 0000000..2ad870f --- /dev/null +++ b/jsmimeDefine.js @@ -0,0 +1,6 @@ +'use strict' +const path = require('path') +define(function (require, exports, module) { + const mainURI = path.join(module.uri, '../jsmimeMain') + return require(mainURI) +}) diff --git a/jsmimeMain.js b/jsmimeMain.js new file mode 100644 index 0000000..2582e2c --- /dev/null +++ b/jsmimeMain.js @@ -0,0 +1,5 @@ +module.exports = { + MimeParser: require('./mimeparser'), + headerparser: require('./headerparser'), + headeremitter: require('./headeremitter') +} diff --git a/mimeparser.js b/mimeparser.js index 5de6927..a5ed4b3 100644 --- a/mimeparser.js +++ b/mimeparser.js @@ -52,12 +52,12 @@ * to the outermost message/rfc822 envelope. */ -define(function(require) { "use strict"; var mimeutils = require('./mimeutils'); var headerparser = require('./headerparser'); var spellings = require('./structuredHeaders').spellings; +const { TextDecoder } = require('./encodings'); /** * An object that represents the structured MIME headers for a message. @@ -1056,5 +1056,4 @@ var ContentDecoders = {}; ContentDecoders['quoted-printable'] = mimeutils.decode_qp; ContentDecoders['base64'] = mimeutils.decode_base64; -return MimeParser; -}); +module.exports = MimeParser; diff --git a/mimeutils.js b/mimeutils.js index 714cfee..2f7e93e 100644 --- a/mimeutils.js +++ b/mimeutils.js @@ -1,4 +1,3 @@ -define(function() { "use strict"; /** @@ -83,11 +82,10 @@ function typedArrayToString(buffer) { const kMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; -return { +module.exports = { decode_base64: decode_base64, decode_qp: decode_qp, kMonthNames: kMonthNames, stringToTypedArray: stringToTypedArray, typedArrayToString: typedArrayToString, }; -}); diff --git a/package.json b/package.json index a015053..d908a02 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "test": "test" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "gulp test", + "mocha-all": "mocha -u tdd -r test test/test_*.js", + "mocha": "mocha -u tdd -r test", + "mocha-debug": "node-debug --hidden node_modules/ node_modules/mocha/bin/_mocha -u tdd -r test" }, "repository": { "type": "git", @@ -17,5 +20,54 @@ "license": "MIT", "bugs": { "url": "https://github.com/mozilla-comm/jsmime/issues" + }, + "devDependencies": { + "babel-core": "^6.4.0", + "babel-plugin-transform-es2015-destructuring": "^6.4.0", + "babel-register": "^6.4.3", + "babelify": "^7.2.0", + "browser-sync": "^2.11.0", + "browserify": "^13.0.0", + "gulp": "^3.9.0", + "gulp-babel-istanbul": "^0.11.0", + "gulp-clean": "^0.3.1", + "gulp-mocha": "^2.2.0", + "gulp-plumber": "^1.0.1", + "gulp-rename": "^1.2.2", + "gulp-sourcemaps": "^1.6.0", + "gulp-uglify": "^1.5.1", + "gulp-util": "^3.0.7", + "mocha": "^2.3.4", + "requirejs": "^2.1.22", + "requirejs-babel": "0.0.8", + "spec-xunit-file": "0.0.1-3", + "vinyl-buffer": "^1.0.0", + "vinyl-source-stream": "^1.1.0", + "watchify": "^3.7.0" + }, + "standard": { + "parser": "babel-eslint", + "globals": [ + "__URI__", + "Components", + "Globals", + "Blob", + "URL", + "FormData", + "describe", + "dump", + "expect", + "it", + "setup", + "suite", + "test", + "define" + ] + }, + "dependencies": { + "atob": "^2.0.0", + "btoa": "^1.1.2", + "legacy-encoding": "^3.0.0", + "text-encoding": "^0.5.2" } } diff --git a/structuredHeaders.js b/structuredHeaders.js index 84a193d..fc65a8a 100644 --- a/structuredHeaders.js +++ b/structuredHeaders.js @@ -3,7 +3,6 @@ * for several key headers. It is not meant to be used externally to jsmime. */ -define(function (require) { "use strict"; var structuredDecoders = new Map(); @@ -161,10 +160,8 @@ function preprocessMessageIDs(values) { structuredDecoders.set("References", preprocessMessageIDs); structuredDecoders.set("In-Reply-To", preprocessMessageIDs); -return Object.freeze({ +module.exports = Object.freeze({ decoders: structuredDecoders, encoders: structuredEncoders, spellings: preferredSpellings, }); - -}); diff --git a/test.js b/test.js new file mode 100644 index 0000000..d913fc3 --- /dev/null +++ b/test.js @@ -0,0 +1,72 @@ +'use strict' +require('babel-register') +const requirejs = require('requirejs') + +global.atob = require('atob') +global.btoa = require('btoa') +global.fs = require('fs') + +let savedConfig = {} +const define = function (id, depends, func) { + // Check if there is a dependency array or if the function is the first arg + if (typeof func === 'undefined') { + if (typeof depends === 'undefined') { + func = id + depends = [] + id = null + } else { + func = depends + depends = id + id = null + } + } + + // Load the modules in depends + var d = [] + for (var i = 0; i < depends.length; i++) { + var m + try { // Try tp load from a requirejs path + m = require(requirejs.toUrl(depends[i])) + } catch (e) { // Fallback to default require + m = require(depends[i]) + } + d.push(m) + } + + // Find the uri of he module that called define + let uri = new Error().stack.split('\n')[2].match(/\((.*):\d*:\d*/)[1] + id = id || uri + let module = { + id: id, + uri: uri, + config: savedConfig, + exports: {}, + } + + require.cache[id] = module + + if (d.length === 0) { + d.push(requirejs, module.exports, module) + } + + const e = func.apply(this, d) + if (e !== undefined) { + module.exports = e + } +} + +define.config = function (c) { + c.nodeRequire = require + savedConfig = c + requirejs.config(c) +} + +define.config({ + baseUrl: __dirname, + paths: { + es6: 'node_modules/requirejs-babel/es6', + babel: 'node_modules/requirejs-babel/babel-5.8.22.min', + jsmime: 'jsmimeDefine' + } +}) +global.define = define diff --git a/test/browser/amd/index.html b/test/browser/amd/index.html index 465d99c..47aeea5 100644 --- a/test/browser/amd/index.html +++ b/test/browser/amd/index.html @@ -2,6 +2,11 @@
+ diff --git a/test/browser/globals/index.html b/test/browser/globals/index.html index eb0899b..6aee741 100644 --- a/test/browser/globals/index.html +++ b/test/browser/globals/index.html @@ -3,6 +3,11 @@ + diff --git a/test/browser/parse_tests.js b/test/browser/parse_tests.js index 9e26365..86b3d28 100644 --- a/test/browser/parse_tests.js +++ b/test/browser/parse_tests.js @@ -3,7 +3,7 @@ function loadTestData(callback) { var xhrreq = new XMLHttpRequest(); xhrreq.onreadystatechange = function () { if (xhrreq.readyState == 4) { - var lines = xhrreq.responseText.split("\n").filter(function (line) { + var lines = xhrreq.responseText.split("\r\n").filter(function (line) { return line[0] == '[' && line != "[DEFAULT]"; }).map(function (line) { return line.slice(1, -1); diff --git a/test/test_mime_tree.js b/test/test_mime_tree.js index 6329df5..980c0e1 100644 --- a/test/test_mime_tree.js +++ b/test/test_mime_tree.js @@ -40,7 +40,12 @@ var file_cache = {}; function read_file(file, start, end) { if (!(file in file_cache)) { var realFile = new Promise(function (resolve, reject) { - fs.readFile("data/" + file, function (err, data) { + var fileURI = "data/" + file + if (typeof __filename !== 'undefined') { + fileURI = __filename + '/../data/' + file + } + + fs.readFile(fileURI, function (err, data) { if (err) reject(err); else resolve(data); }); @@ -83,8 +88,11 @@ function read_file(file, start, end) { * of the file from [line start, line end) [1-based lines] */ function make_body_test(test, file, opts, partspec) { - var results = Promise.all([ - Promise.all([p[0], read_file(file, p[1], p[2])]) for (p of partspec)]); + let readFilePromises = [] + for (let p of partspec) { + readFilePromises.push(Promise.all([p[0], read_file(file, p[1], p[2])])) + } + var results = Promise.all(readFilePromises); var eol = extract_field(opts, "_eol"); var msgtext = read_file(file).then(function(msgcontents) { var packetize = extract_field(opts, "_split"); @@ -187,7 +195,10 @@ function testParser(message, opts, results) { let [message, results] = vals; // Clone the results array into uncheckedValues if (Array.isArray(results)) { - uncheckedValues = [for (val of results) val]; + uncheckedValues = [] + for (let val of results) { + uncheckedValues.push(val); + } checkingHeaders = false; } else { uncheckedValues = {}; diff --git a/test/test_structured_headers.js b/test/test_structured_headers.js index e065efb..973d27c 100644 --- a/test/test_structured_headers.js +++ b/test/test_structured_headers.js @@ -5,10 +5,14 @@ var assert = require('assert'); var headerparser = require('jsmime').headerparser; function smartDeepEqual(actual, expected) { + if (actual instanceof Date && expected instanceof Date) { + assert.equal(actual.toString(), expected.toString()); + return + } assert.deepEqual(actual, expected); if (actual instanceof Map && expected instanceof Map) { - assert.deepEqual([x for (x of actual.entries())], - [y for (y of expected.entries())]); + assert.deepEqual(actual.entries(), + expected.entries()); } }