From 653e54c846f07459607d69e208e2593fb55f5e1f Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Tue, 31 Mar 2020 17:27:36 +0000 Subject: [PATCH] Prepare first release --- cli.mjs | 64 ++++++++ index.mjs | 7 + lib/assert.mjs | 83 ++++++++++ lib/casette.mjs | 263 +++++++++++++++++++++++++++++ lib/cli.mjs | 169 +++++++++++++++++++ package.json | 10 +- test/assert.test.mjs | 154 +++++++++++++++++ test/casette.test.mjs | 192 ++++++++++++++++++++++ test/cli.test.mjs | 265 ++++++++++++++++++++++++++++++ test/folder1/sampletest1.temp.mjs | 0 test/folder1/sampletest2.temp.mjs | 0 test/folder2/sampletest3.temp.mjs | 0 test/folder2/sampletest4.temp.mjs | 0 test/folder2/sampletest5.temp.txt | 1 + test/test.mjs | 9 + 15 files changed, 1215 insertions(+), 2 deletions(-) create mode 100644 cli.mjs create mode 100644 index.mjs create mode 100644 lib/assert.mjs create mode 100644 lib/casette.mjs create mode 100644 lib/cli.mjs create mode 100644 test/assert.test.mjs create mode 100644 test/casette.test.mjs create mode 100644 test/cli.test.mjs create mode 100644 test/folder1/sampletest1.temp.mjs create mode 100644 test/folder1/sampletest2.temp.mjs create mode 100644 test/folder2/sampletest3.temp.mjs create mode 100644 test/folder2/sampletest4.temp.mjs create mode 100644 test/folder2/sampletest5.temp.txt create mode 100644 test/test.mjs diff --git a/cli.mjs b/cli.mjs new file mode 100644 index 0000000..e5f8a3b --- /dev/null +++ b/cli.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +// Get arguments +const [,, ...args] = process.argv + +import c from './lib/casette.mjs' +import { CLI, printError } from './lib/cli.mjs' + +c.begin() + +const cli = new CLI(c) +cli.parseOptions(args) + +if (cli.errored) { + PrintHelp() +} + +function PrintHelp() { + console.log('') + console.log('Usage: casette ') + console.log('') + console.log('where can either be a single file or a simple glob pattern.') + console.log('where can be any of the following:') + console.log(' -r, --reporter - Specify the reporter to use.') + console.log(' Supported reporters: list, dot') + console.log('') + console.log('casette test/mytest.mjs') + console.log('casette dot test/*.mjs') + console.log('casette -r dot test/**/*.test.mjs') + process.exit(1) +} + +cli.processTargets().then(function() { + if (!cli.files.length) { + console.log('') + console.log('No files were found with pattern', cli.targets.join(',')) + PrintHelp() + } + return cli.loadFiles() + .then(function() { + c.reporter = cli.reporter + + return c.run() + .catch(function(err) { + console.log('') + console.error('\x1b[31mUnknown error occured while running the tests\x1b[0m') + printError(err) + process.exit(1) + }) + }, function(err) { + console.log('') + console.error('\x1b[31mUnknown error while opening files\x1b[0m') + printError(err) + process.exit(1) + }) +}, function(err) { + console.log('') + console.error('\x1b[31mUnknown error while processing arguments\x1b[0m') + printError(err) + process.exit(1) +}) +.then(function() { + process.exit(0) +}) diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..a098852 --- /dev/null +++ b/index.mjs @@ -0,0 +1,7 @@ +import Casette from './lib/casette.mjs' +import assert from './lib/assert.mjs' + +export { + Casette, + assert, +} diff --git a/lib/assert.mjs b/lib/assert.mjs new file mode 100644 index 0000000..0d7cc06 --- /dev/null +++ b/lib/assert.mjs @@ -0,0 +1,83 @@ +import assert from 'assert' +import util from 'util' + +const fail = assert.fail; + +function truncate(s, n) { + return s.length < n ? s : s.slice(0, n) + '...'; +} + +function stringifyObject(data) { + if (typeof(data) !== 'string') { + data = util.inspect( + data, + { depth: 1 } + ) + .replace(/\n /g, ''); + } + return truncate(data, 64); +} + +assert.notOk = (value, message) => { + if (Boolean(value)) { + assert.equal(value, false, message) + } +} + +assert.match = (value, test, message) => { + let result = value.match(test); + if (result) return; + + let m = message + if (!m) { + m = `${value} did not match ${test}` + } + + fail(m); +} + +assert.notMatch = (value, test, message) => { + let result = value.match(test); + if (!result) return; + + let m = message + if (!m) { + m = `${value} matched ${test}` + } + + fail(m); +} + +assert.isFulfilled = (promise, message) => { + return Promise.resolve(true) + .then(() => promise) + .catch((err) => { + let m = message + if (!m) { + m = `promise failed with ${err.message || stringifyObject(err)}`; + } + fail(m); + }); +} + +assert.isRejected = (promise, message) => { + let hasFailed = false; + + return Promise.resolve(true) + .then(() => promise) + .catch((data) => { + hasFailed = true; + return data; + }) + .then((data) => { + if (hasFailed) return data; + let m = message + if (!m) { + m = `promise was fulfilled with ${stringifyObject(data)}`; + } + + fail(m); + }); +} + +export default assert diff --git a/lib/casette.mjs b/lib/casette.mjs new file mode 100644 index 0000000..a97e1f9 --- /dev/null +++ b/lib/casette.mjs @@ -0,0 +1,263 @@ +import { printError } from './cli.mjs' + +function Group(name) { + this.name = name + this.tests = [] +} + +function Test(name, func) { + this.skipTest = false + this.customTimeout = null + this.name = name + this.func = func + this.group = null + this.error = null +} + +Test.prototype.timeout = function(time) { + this.customTimeout = time +} + +Test.prototype.skip = function() { + this.skipTest = true +} + +function Casette() { + this.__timeout = 2000 + this.reporter = 'list' + this.Casette = Casette + this.groups = new Map() + this.groupsFlat = [] + this.tests = [] + this.failedTests = [] + this.hasTests = false + this.starting = false + this.filename = '' + this.prefix = '' +} + +Casette.prototype.begin = function() { + if (this.starting) { + console.warn('WARNING: Multiple calls to Casette.begin were done.') + return + } + this.hasTests = false + this.starting = true + this.filename = '' + this.prefix = '' + this.groups.clear() + this.tests.splice(0, this.tests.length) +} + +Casette.prototype.__runTest = async function(stats, test) { + if (this.reporter === 'list') { + process.stdout.write(' \x1b[90m? ' + test.name + '\x1b[0m') + } + + if (!test.skipTest) { + await new Promise((resolve, reject) => { + // Flag to check if we finished + let finished = false + let timeout = test.customTimeout || this.__timeout + + // Timeout timer in case test times out + let timer = setTimeout(function() { + if (finished === true) return + reject(new Error('timeout of ' + timeout + 'ms exceeded. Ensure the done() callback is being called in this test.')) + }, timeout) + + // start the test runner + try { + // Does it accept a callback + let checkIsCallback = (test.func.toString()).match(/^(function)? *\([^\)]+\)/) + let promise + + // If the test requires callback, wrap it in a promise where callback + // either resolves or rejects that promise + if (checkIsCallback) { + promise = new Promise(function(res, rej) { + test.func(function(err) { + if (err) { + return rej(err) + } + res() + }) + }) + } else { + // Function doesn't require a callback, run it directly + promise = test.func() + } + + // Check if the function we ran returned a promise + if (promise && promise.then && typeof(promise.then === 'function')) { + // If the promise from the function succeeded, resolve our promise. + // Otherwise reject it + promise.then(function() { + // check if our test had already finished and if so, do nothing + if (finished === true) return + + finished = true + clearTimeout(timer) + resolve() + }, function(err) { + // check if our test had already finished and if so, do nothing + if (finished === true) return + + finished = true + clearTimeout(timer) + reject(err) + }) + } else { + // check if our test had already finished and if so, do nothing + if (finished === true) return + + // Possible this was a synchronous test, pass immediately + finished = true + clearTimeout(timer) + resolve() + } + } catch (err) { + // check if our test had already finished and if so, do nothing + if (finished === true) return + + // An error occured while running function. Possible exception + // during a synchronous test or something else. + finished = true + clearTimeout(timer) + reject(err) + } + }) + .then(function() { + stats.passed++ + }, function(err) { + test.error = err + stats.failed++ + } + ) + } else { + stats.skipped++ + } + + if (test.error) { + this.failedTests.push(test) + } + + if (this.reporter === 'list') { + process.stdout.clearLine(); + process.stdout.cursorTo(0); + if (test.skipTest) { + process.stdout.write(' \x1b[94m- ' + test.name + '\x1b[0m\n') + } else if (!test.error) { + process.stdout.write(' \x1b[32m√\x1b[90m ' + test.name + '\x1b[0m\n') + } else { + process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + test.name + '\x1b[0m\n') + } + } else if (this.reporter === 'dot') { + if (test.skipTest) { + process.stdout.write('\x1b[94m.\x1b[0m') + } else if (!test.error) { + process.stdout.write('\x1b[32m.\x1b[0m') + } else { + process.stdout.write('\x1b[31m.\x1b[0m') + } + } +} + +Casette.prototype.run = async function() { + if (this.reporter) { + console.log('') + console.log('') + } + + let stats = { + passed: 0, + failed: 0, + skipped: 0, + } + + let start = process.hrtime() + for (let i = 0; i < this.groupsFlat.length; i++) { + let g = this.groupsFlat[i]; + + if (this.reporter === 'list') { + console.log(' ' + g.name) + } + for (let x = 0; x < g.tests.length; x++) { + await this.__runTest(stats, g.tests[x]) + } + } + + for (let x = 0; x < this.tests.length; x++) { + await this.__runTest(stats, this.tests[x]) + } + let end = process.hrtime(start) + + if (this.reporter) { + console.log('') + console.log('') + if (stats.passed) { + console.log(' \x1b[32m' + stats.passed + ' passing \x1b[90m(' + (end[0] * 1000 + Math.round(end[1] / 1000000)) + 'ms)\x1b[0m') + } + if (stats.failed) { + console.log(' \x1b[31m' + stats.failed + ' failing\x1b[0m') + } + if (stats.skipped) { + console.log(' \x1b[94m' + stats.skipped + ' pending\x1b[0m') + } + console.log('') + + if (this.failedTests.length) { + for (let x = 0; x < this.failedTests.length; x++) { + let test = this.failedTests[x]; + console.log(' ' + (x + 1) + ') ' + + (test.group ? test.group.name + ': ' : '' ) + + test.name + ':' + ) + printError(test.error) + } + } + } +} + +Casette.prototype.setFilename = function(filename) { + this.filename = filename +} + +Casette.prototype.resetFilename = function() { + this.filename = '' +} + +Casette.prototype.describe = function(name, func) { + let before = this.prefix + if (before) { + this.prefix = before + ' ' + name + } else { + this.prefix = name + } + func() + this.prefix = before +} + +Casette.prototype.test = function(name, func) { + let targetName = name + if (this.prefix) { + targetName = this.prefix + ' ' + name + } + this.hasTests = true + + let group = this + if (this.filename) { + if (!this.groups.has(this.filename)) { + let g = new Group(this.filename) + this.groupsFlat.push(g) + this.groups.set(this.filename, g) + } + group = this.groups.get(this.filename) + } + let test = new Test(targetName, func) + test.group = group + group.tests.push(test) + return test +} + +export default new Casette() diff --git a/lib/cli.mjs b/lib/cli.mjs new file mode 100644 index 0000000..8e16777 --- /dev/null +++ b/lib/cli.mjs @@ -0,0 +1,169 @@ +import path from 'path' +import fs from 'fs' + +export function CLI(c) { + this.c = c + this.reporter = 'list' + this.targets = ['test/**'] + this.files = [] + this.errored = false +} + +CLI.prototype.parseOptions = function(args) { + if (!args || !args.length) { + this.targets.push('test/**') + this.errored = false + return + } + + this.errored = false + this.targets.splice(0, this.targets.length) + + + for (let i = 0; i < args.length; i++) { + if (args[i] === '-r' || args[i] === '--reporter') { + if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) { + this.errored = true + return + } + this.reporter = args[i + 1] + i++ + } else { + this.targets.push(args[i]) + } + } + + if (!this.targets.length) { + this.targets.push('test/**') + } +} + +CLI.prototype.processTargets = function() { + this.files.splice(0, this.files.length) + + if (!this.targets.length) { + return Promise.resolve() + } + + return Promise.all(this.targets.map((target) => { + return getFiles(this.files, target) + })).then(() => { + if (!this.files.length) { + this.errored = 'empty' + } + }) +} + +CLI.prototype.loadFiles = async function() { + let cwd = process.cwd() + + for (let i = 0; i < this.files.length; i++) { + if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) { + this.c.setFilename(this.files[i]) + await import('file:///' + path.join(cwd, this.files[i])) + this.c.resetFilename() + } + } +} + +function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, includeFiles) { + return new Promise(function(resolve, reject) { + return fs.readdir(curr, function(err, data) { + if (err) return reject(new Error('unable to read directory ' + curr + ': ' + err.message)) + + resolve(Promise.all(data.map(function(file) { + return new Promise(function(res, rej) { + fs.lstat(path.join(curr, file), function(err, stat) { + if (err) return rej(new Error('unable to read file or directory ' + path.join(curr, file) + ': ' + err.message)) + + if ((includeFiles || grabAll) && stat.isFile()) { + if (!match || fileMatches(file, match)) { + files.push(path.join(insidePath, file).replace(/\\/g, '/')) + return res(files) + } + } + if (stat.isDirectory() && grabAll) { + return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles)) + } else if (stat.isDirectory() && match) { + return res(getFiles(files, match, path.join(insidePath, file), grabAll, insideStar)) + } + res(null) + }) + }) + })).then(function() { return files })) + }) + }) +} + +export function fileMatches(filename, match) { + return Boolean(filename.match(new RegExp(match.replace(/\./, '\\.').replace(/\*/, '.*')))) +} + +export function getFiles(files, match, insidePath, grabAll, insideStar) { + let isGrabbingAll = grabAll || false + let isStarred = insideStar || false + let cwd = process.cwd() + let currPath = insidePath || '' + let curr = path.join(cwd, currPath || '') + + return new Promise(function(res, rej) { + let start = 0 + let splitted = match.split('/') + if (splitted[start] === '.') { + start++ + } + if (splitted[splitted.length - 1] === '') { + splitted[splitted.length - 1] === '*' + } + + let first = splitted[start] + + if (splitted.length > start + 1) { + if (first === '**') { + isGrabbingAll = isStarred = true + return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false) + .then(res, rej) + } else if (first === '*') { + isStarred = true + return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false) + .then(res, rej) + } + return getFiles(files, splitted.slice(start + 1).join('/'), path.join(currPath, first), grabAll, isStarred) + .then(res, rej) + } else if (first.indexOf('*') >= 0) { + if (first === '**') { + isGrabbingAll = isStarred = true + } + return traverseFolder(files, curr, first === '*' || first === '**'? '' : first, currPath, isGrabbingAll, isStarred, true) + .then(res, rej) + } + + fs.lstat(path.join(curr, first), function(err, stat) { + if (err) { + // If we're inside a star, we ignore files we cannot find + if (isStarred) { + return res(files) + } + return rej(new Error('file ' + path.join(insidePath, first) + ' could not be found: ' + err.message)) + } + + if (stat.isDirectory()) { + return traverseFolder(files, path.join(curr, first), '', path.join(currPath, first), true, true, true) + .then(res, rej) + } + + files.push(path.join(currPath, match).replace(/\\/g, '/')) + return res(files) + }) + }) +} + +export function printError(err, msg) { + let before = msg || '' + console.error('') + console.error('\x1b[31m ' + + before + err.toString() + + '\x1b[0m\n \x1b[90m' + + err.stack.replace(err.toString(), '')) + console.error('\x1b[0m') +} diff --git a/package.json b/package.json index c304e6c..6007e7c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "casette", "version": "0.9.0", "description": "No-dependancy test framework for node", - "main": "index.js", + "main": "index.mjs", "scripts": { "test": "node cli.mjs test/**/*.test.mjs" }, @@ -25,5 +25,11 @@ "homepage": "https://github.com/TheThing/node-casette#readme", "bin": { "casette": "./cli.mjs" - } + }, + "files": [ + "index.mjs", + "cli.mjs", + "README.md", + "lib" + ] } diff --git a/test/assert.test.mjs b/test/assert.test.mjs new file mode 100644 index 0000000..5fa6c3b --- /dev/null +++ b/test/assert.test.mjs @@ -0,0 +1,154 @@ +import util from 'util' +import assert from 'assert' +import assertExtended from '../lib/assert.mjs' + +import c from '../lib/casette.mjs' + +const testLongObject = { + a: 1, b:2, c:3, d:4, + e: {herp: 51, derp: 23}, + f: 'asdfgagwegawegawegawegawe', + g: '32ghaiwugb23 238023' +} + +c.describe('#notOk()', function() { + c.test('should exist', function() { + assertExtended.ok(assertExtended.notOk) + }) + + c.test('should throw for true values', function() { + assertExtended.throws(function() { + assertExtended.notOk(true) + }, assertExtended.AssertionError) + }) + + c.test('should pass for false values', function() { + assertExtended.notOk(false) + assertExtended.notOk(null) + assertExtended.notOk(0) + }) +}) + +c.describe('#isFulfilled()', function() { + c.test('should exist', function() { + assertExtended.ok(assertExtended.isFulfilled) + }) + + c.test('should throw for rejected promises', function() { + return assertExtended.isFulfilled(Promise.reject({})) + .catch((err) => { + assertExtended.ok(err.message.match(/promise fail/)) + }) + }) + + c.test('should properly parse rejected object response', function() { + let assertMessage = util.inspect(testLongObject, {depth: 1}).replace(/\n /g, '') + assertMessage = assertMessage.slice(0, 64) + '...' + + return assertExtended.isFulfilled(Promise.reject(testLongObject)) + .catch((err) => + assertExtended.notStrictEqual(err.message.indexOf(assertMessage), -1) + ) + }) + + c.test('should include error message if error', function() { + const assertMessage = 'something something dark side' + return assertExtended.isFulfilled(Promise.reject(new Error(assertMessage))) + .catch((err) => { + assertExtended.ok(err.message.match(new RegExp('with ' + assertMessage))) + }) + }) + + c.test('should pass for resolved promises', function() { + return assertExtended.isFulfilled(Promise.resolve()) + }) + + c.test('should support custom message', function() { + const assertMessage = 'something something dark side' + return assertExtended.isFulfilled(Promise.reject({}), assertMessage) + .catch((err) => { + assertExtended.ok(err.message.match(assertMessage)) + }) + }) + + c.test('should return result for the resolved promise', function() { + const assertResult = {a: 1} + + return assertExtended.isFulfilled(Promise.resolve(assertResult)) + .then((data) => assertExtended.strictEqual(data, assertResult)) + }) +}) + +c.describe('#isRejected()', function() { + c.test('should exist', function() { + assertExtended.ok(assertExtended.isRejected) + }) + + c.test('should throw for resolved promises', function() { + let hasFailed = false + + return assertExtended.isRejected(Promise.resolve({})) + .catch((err) => { + hasFailed = true + assertExtended.ok(err.message.match(/fulfilled with/)) + }) + .then(function() { + assertExtended.strictEqual(hasFailed, true) + }) + }) + + c.test('should properly stringify objects', function() { + let assertMessage = util.inspect(testLongObject, {depth: 1}).replace(/\n /g, '') + assertMessage = assertMessage.slice(0, 64) + '...' + + return assertExtended.isRejected(Promise.resolve(testLongObject)) + .catch((err) => + assertExtended.notStrictEqual(err.message.indexOf(assertMessage), -1) + ) + }) + + c.test('should support custom message', function() { + const assertMessage = 'something something dark side' + return assertExtended.isRejected(Promise.resolve({}), assertMessage) + .catch((err) => assertExtended.ok(err.message.match(assertMessage))) + }) + + c.test('should return result for the unresolved promise', function() { + const assertResult = {a: 1} + + return assertExtended.isRejected(Promise.reject(assertResult)) + .then((data) => assertExtended.strictEqual(data, assertResult)) + }) +}) + +c.describe('#match()', function() { + c.test('should exist', function() { + assertExtended.ok(assertExtended.match); + }); + + c.test('should throw if no match', function() { + assertExtended.throws(function() { + assertExtended.match('a', /b/); + }, assertExtended.AssertionError); + }); + + c.test('should pass if matches', function() { + assertExtended.match('a', /a/); + }); +}) + +c.describe('#notMatch()', function() { + c.test('should exist', function() { + assertExtended.ok(assertExtended.notMatch); + }); + + c.test('should throw if match', function() { + assertExtended.throws(function() { + assertExtended.notMatch('a', /a/); + }, assertExtended.AssertionError); + }); + + c.test('should pass if not matches', function() { + assertExtended.notMatch('a', /b/); + }); +}) diff --git a/test/casette.test.mjs b/test/casette.test.mjs new file mode 100644 index 0000000..fb42fbd --- /dev/null +++ b/test/casette.test.mjs @@ -0,0 +1,192 @@ +import c from '../lib/casette.mjs' +import assert from '../lib/assert.mjs' +import { printError } from '../lib/cli.mjs' + +let testsWereRun = false + +function CreateT() { + const t = new c.Casette() + t.reporter = '' + return t +} + +c.test('Casette describe should add prefix to the group tests', async function() { + testsWereRun = true + const assertPrefix = 'something' + const assertName = 'blabla' + const t = CreateT() + t.begin() + t.setFilename('test') + t.describe(assertPrefix, function() { + t.test(assertName, function() {}) + }) + + assert.strictEqual(t.groupsFlat.length, 1) + assert.strictEqual(t.groupsFlat[0].tests.length, 1) + assert.strictEqual(t.groupsFlat[0].tests[0].name, assertPrefix + ' ' + assertName) +}) + +c.test('Casette describe should add prefix to individual tests', async function() { + testsWereRun = true + const assertPrefix = 'something' + const assertName = 'blabla' + const t = CreateT() + t.begin() + t.describe(assertPrefix, function() { + t.test(assertName, function() {}) + }) + + assert.strictEqual(t.tests.length, 1) + assert.strictEqual(t.tests[0].name, assertPrefix + ' ' + assertName) +}) + +c.test('Casette describe should support multiple describe', async function() { + testsWereRun = true + const assertPrefix = 'something' + const assertPrefix2 = 'else' + const assertName = 'blabla' + const t = CreateT() + t.begin() + t.describe(assertPrefix, function() { + t.describe(assertPrefix2, function() { + t.test(assertName, function() {}) + }) + }) + + assert.strictEqual(t.tests.length, 1) + assert.strictEqual(t.tests[0].name, assertPrefix + ' ' + assertPrefix2 + ' ' + assertName) +}) + +c.test('Casette should run test', async function() { + testsWereRun = true + let assertIsTrue = false + const t = CreateT() + t.begin() + t.test('', function() { + assertIsTrue = true + }) + await t.run() + assert.strictEqual(t.failedTests.length, 0) + assert.strictEqual(assertIsTrue, true) +}) + +c.test('Casette should run promised test', async function() { + testsWereRun = true + let assertIsTrue = false + const t = CreateT() + t.begin() + t.test('', function() { + return new Promise(function(res) { + assertIsTrue = true + res() + }) + }) + await t.run() + assert.strictEqual(t.failedTests.length, 0) + assert.strictEqual(assertIsTrue, true) +}) + +c.test('Casette should support callback', async function() { + testsWereRun = true + let assertIsTrue = false + const t = CreateT() + t.begin() + t.test('', function(cb) { + setTimeout(function() { + assertIsTrue = true + cb() + }, 50) + }) + await t.run() + assert.strictEqual(t.failedTests.length, 0) + assert.strictEqual(assertIsTrue, true) +}) + +c.test('Casette should support directly thrown errors', async function() { + testsWereRun = true + const assertError = new Error() + const t = CreateT() + t.begin() + t.test('', function() { + throw assertError + }) + await t.run() + assert.strictEqual(t.failedTests.length, 1) + assert.strictEqual(t.failedTests[0].error, assertError) +}) + +c.test('Casette should support promise rejected errors', async function() { + testsWereRun = true + const assertError = new Error() + const t = CreateT() + t.begin() + t.test('', function() { + return new Promise(function(res, rej) { + rej(assertError) + }) + }) + await t.run() + assert.strictEqual(t.failedTests.length, 1) + assert.strictEqual(t.failedTests[0].error, assertError) +}) + +c.test('Casette should support callback rejected errors', async function() { + testsWereRun = true + const assertError = new Error() + const t = CreateT() + t.begin() + t.test('', function(cb) { + cb(assertError) + }) + await t.run() + assert.strictEqual(t.failedTests.length, 1) + assert.strictEqual(t.failedTests[0].error, assertError) +}) + +c.test('Casette should support timing out tests', async function() { + testsWereRun = true + const t = CreateT() + t.begin() + t.test('', function(cb) { }).timeout(50) + await t.run() + assert.strictEqual(t.failedTests.length, 1) + assert.ok(t.failedTests[0].error) + assert.match(t.failedTests[0].error.message, /50ms/) +}) + +c.test('Casette should support timed out tests on late tests', async function() { + testsWereRun = true + const t = CreateT() + t.begin() + t.test('', function(cb) { + setTimeout(function() { + cb() + }, 100) + }).timeout(50) + await t.run() + assert.strictEqual(t.failedTests.length, 1) + assert.ok(t.failedTests[0].error) + assert.match(t.failedTests[0].error.message, /50ms/) +}) + +c.test('Casette should support skipped tests', async function() { + testsWereRun = true + const t = CreateT() + t.begin() + t.test('', function() { + throw new Error('Should not be called') + }).skip() + await t.run() + assert.strictEqual(t.failedTests.length, 0) +}) + +// Extra testing to make sure tests were run at all +process.on('exit', function(e) { + try { + assert.strictEqual(testsWereRun, true) + } catch(err) { + printError(err) + process.exit(1) + } + process.exit(e) +}) diff --git a/test/cli.test.mjs b/test/cli.test.mjs new file mode 100644 index 0000000..5b3d112 --- /dev/null +++ b/test/cli.test.mjs @@ -0,0 +1,265 @@ +import c from '../lib/casette.mjs' +import assert from '../lib/assert.mjs' +import { CLI, getFiles, fileMatches } from '../lib/cli.mjs' + +c.describe('CLI', function() { + let cli = new CLI() + + c.test('#constructor() give default options', function() { + assert.strictEqual(cli.reporter, 'list') + assert.deepEqual(cli.targets, ['test/**']) + assert.deepEqual(cli.files, []) + assert.notOk(cli.errored) + }) + + /***************************************** + * #parseOptions() + *****************************************/ + + c.describe('#parseOptions()', function() { + c.test('should not do anything if no options', function() { + cli.reporter = 'list' + cli.parseOptions([]) + assert.strictEqual(cli.reporter, 'list') + assert.notOk(cli.errored) + }) + + c.test('should support overriding reporter with shorthand option', function() { + cli.reporter = 'list' + cli.parseOptions(['-r', 'dot']) + assert.strictEqual(cli.reporter, 'dot') + assert.notOk(cli.errored) + }) + + c.test('should support overriding reporter with long option', function() { + cli.reporter = 'list' + cli.parseOptions(['--reporter', 'dot']) + assert.strictEqual(cli.reporter, 'dot') + assert.notOk(cli.errored) + }) + + c.test('should support reporter list', function() { + cli.reporter = 'list' + cli.parseOptions(['-r', 'list']) + assert.strictEqual(cli.reporter, 'list') + assert.notOk(cli.errored) + }) + + c.test('should mark errored if missing reporter', function() { + cli.parseOptions(['--reporter']) + assert.ok(cli.errored) + }) + + c.test('should mark errored if invalid reporter', function() { + cli.parseOptions(['--reporter', 'test']) + assert.ok(cli.errored) + }) + + c.test('should add file to targets', function() { + cli.parseOptions(['test']) + assert.deepEqual(cli.targets, ['test']) + assert.notOk(cli.errored) + }) + + c.test('should add file to targets no matter where it is', function() { + cli.parseOptions(['test', '-r', 'list', 'test2']) + assert.deepEqual(cli.targets, ['test', 'test2']) + assert.notOk(cli.errored) + }) + + c.test('should default add test to target if no target', function() { + cli.parseOptions(['-r', 'list']) + assert.deepEqual(cli.targets, ['test/**']) + assert.notOk(cli.errored) + }) + }) + + /***************************************** + * #processTargets() + *****************************************/ + + c.describe('#processTargets()', function() { + c.test('should mark errored if empty', async function() { + cli.targets = ['test/folder1/*.txt'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 0) + assert.ok(cli.errored) + }) + + c.test('should support direct file path if exists', async function() { + cli.targets = ['test/folder1/sampletest1.temp.mjs'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 1) + assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs') + }) + + c.test('should return all files in a directory', async function() { + cli.targets = ['test/folder1/'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 2) + assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs') + assert.strictEqual(cli.files[1], 'test/folder1/sampletest2.temp.mjs') + }) + + c.test('should support start as folder substitute', async function() { + cli.targets = ['*/folder1/'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 2) + assert.strictEqual(cli.files[0], 'test/folder1/sampletest1.temp.mjs') + assert.strictEqual(cli.files[1], 'test/folder1/sampletest2.temp.mjs') + }) + + c.test('should support grabbing only files in folder', async function() { + cli.targets = ['test/*'] + await cli.processTargets() + + assert.ok(cli.files.length) + for (let i = 0; i < cli.files.length; i++) { + assert.notOk(cli.files[i].match(/\/folder1\//)) + assert.notOk(cli.files[i].match(/\/folder2\//)) + } + }) + + c.test('should support grabbing only pattern files in folder', async function() { + cli.targets = ['test/*.test.mjs'] + await cli.processTargets() + + assert.ok(cli.files.length) + for (let i = 0; i < cli.files.length; i++) { + assert.notOk(cli.files[i].match(/\/folder1\//)) + assert.notOk(cli.files[i].match(/\/folder2\//)) + } + }) + + c.test('should support multiple star pattern', async function() { + cli.targets = ['test/*/*.mjs'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 4) + cli.files.sort() + assert.deepEqual(cli.files, [ + 'test/folder1/sampletest1.temp.mjs', + 'test/folder1/sampletest2.temp.mjs', + 'test/folder2/sampletest3.temp.mjs', + 'test/folder2/sampletest4.temp.mjs', + ]) + + cli.targets = ['test/*/sampletest*.mjs'] + await cli.processTargets() + + assert.strictEqual(cli.files.length, 4) + cli.files.sort() + assert.deepEqual(cli.files, [ + 'test/folder1/sampletest1.temp.mjs', + 'test/folder1/sampletest2.temp.mjs', + 'test/folder2/sampletest3.temp.mjs', + 'test/folder2/sampletest4.temp.mjs', + ]) + }) + + c.test('should support double star pattern', async function() { + cli.targets = ['test/**/*.mjs'] + await cli.processTargets() + + assert.ok(cli.files.length) + + let found = { + sampletest1: false, + sampletest2: false, + sampletest3: false, + sampletest4: false, + sampletest5: false, + cli: false + } + + for (let i = 0; i < cli.files.length; i++) { + if (cli.files[i] === 'test/folder1/sampletest1.temp.mjs') { + found.sampletest1 = true + } + if (cli.files[i] === 'test/folder1/sampletest2.temp.mjs') { + found.sampletest2 = true + } + if (cli.files[i] === 'test/folder2/sampletest3.temp.mjs') { + found.sampletest3 = true + } + if (cli.files[i] === 'test/folder2/sampletest4.temp.mjs') { + found.sampletest4 = true + } + if (cli.files[i] === 'test/folder2/sampletest5.temp.txt') { + found.sampletest5 = true + } + if (cli.files[i] === 'test/cli.test.mjs') { + found.cli = true + } + } + + assert.deepEqual(found, { + sampletest1: true, + sampletest2: true, + sampletest3: true, + sampletest4: true, + sampletest5: false, + cli: true + }) + }) + + c.test('should support double star pattern end', async function() { + cli.targets = ['test/**'] + await cli.processTargets() + + assert.ok(cli.files.length) + + let found = { + sampletest1: false, + sampletest2: false, + sampletest3: false, + sampletest4: false, + sampletest5: false, + cli: false + } + + for (let i = 0; i < cli.files.length; i++) { + if (cli.files[i] === 'test/folder1/sampletest1.temp.mjs') { + found.sampletest1 = true + } + if (cli.files[i] === 'test/folder1/sampletest2.temp.mjs') { + found.sampletest2 = true + } + if (cli.files[i] === 'test/folder2/sampletest3.temp.mjs') { + found.sampletest3 = true + } + if (cli.files[i] === 'test/folder2/sampletest4.temp.mjs') { + found.sampletest4 = true + } + if (cli.files[i] === 'test/folder2/sampletest5.temp.txt') { + found.sampletest5 = true + } + if (cli.files[i] === 'test/cli.test.mjs') { + found.cli = true + } + } + + assert.deepEqual(found, { + sampletest1: true, + sampletest2: true, + sampletest3: true, + sampletest4: true, + sampletest5: true, + cli: true + }) + }) + }) +}) + +c.test('#fileMatches() should support filename matching with glob pattern', async function() { + assert.ok(fileMatches('bla.test.mjs', '*.mjs')) + assert.ok(fileMatches('bla.test.mjs', '*test.mjs')) + assert.ok(fileMatches('bla.test.mjs', 'bla*.mjs')) + assert.notOk(fileMatches('bla.test.mjs', 'bla*.js')) + assert.notOk(fileMatches('bla.test.mjs', '*.js')) + assert.notOk(fileMatches('bla.test.mjs', 'blas*.js')) +}) diff --git a/test/folder1/sampletest1.temp.mjs b/test/folder1/sampletest1.temp.mjs new file mode 100644 index 0000000..e69de29 diff --git a/test/folder1/sampletest2.temp.mjs b/test/folder1/sampletest2.temp.mjs new file mode 100644 index 0000000..e69de29 diff --git a/test/folder2/sampletest3.temp.mjs b/test/folder2/sampletest3.temp.mjs new file mode 100644 index 0000000..e69de29 diff --git a/test/folder2/sampletest4.temp.mjs b/test/folder2/sampletest4.temp.mjs new file mode 100644 index 0000000..e69de29 diff --git a/test/folder2/sampletest5.temp.txt b/test/folder2/sampletest5.temp.txt new file mode 100644 index 0000000..c693f13 --- /dev/null +++ b/test/folder2/sampletest5.temp.txt @@ -0,0 +1 @@ +keep \ No newline at end of file diff --git a/test/test.mjs b/test/test.mjs new file mode 100644 index 0000000..ed0271b --- /dev/null +++ b/test/test.mjs @@ -0,0 +1,9 @@ +import { Casette as c, assert} from '../index.mjs' + +c.describe('Array', function() { + c.describe('#indexOf()', function() { + c.test('should return -1 when value is not present', function() { + assert.equal([1,2,3].indexOf(4), -1) + }) + }) +})