diff --git a/bsc-plugin/package-lock.json b/bsc-plugin/package-lock.json index 396a9fae..7825e735 100644 --- a/bsc-plugin/package-lock.json +++ b/bsc-plugin/package-lock.json @@ -1304,9 +1304,9 @@ } }, "node_modules/@postman/tunnel-agent": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", - "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz", + "integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -2327,9 +2327,10 @@ } }, "node_modules/brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.67.4", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.4.tgz", + "integrity": "sha512-1EbX2Po9WbIyi4r557fk6txkYR0VvbnGm1caS9AVlVek2R6d9+QsQxr+f24PePM4l/TOBvZ1JieBt6NheM4P2Q==", + "dev": true, "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@rokucommunity/logger": "^0.3.9", @@ -2373,6 +2374,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -2386,6 +2388,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dev": true, "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, @@ -7210,13 +7213,13 @@ } }, "node_modules/postman-request": { - "version": "2.88.1-postman.33", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", - "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "dependencies": { "@postman/form-data": "~3.1.1", "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.3", + "@postman/tunnel-agent": "^0.6.4", "aws-sign2": "~0.7.0", "aws4": "^1.12.0", "brotli": "^1.3.3", @@ -7238,7 +7241,7 @@ "uuid": "^8.3.2" }, "engines": { - "node": ">= 6" + "node": ">= 16" } }, "node_modules/postman-request/node_modules/http-signature": { @@ -8427,6 +8430,87 @@ "roku-debug": "dist/cli.js" } }, + "node_modules/roku-debug/node_modules/brighterscript": { + "version": "0.67.8", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.8.tgz", + "integrity": "sha512-2pQcPzW/NCEEofojLuHRDcLuQTmHssNpj1xW2oRiSFMo0YHR5cBIHrG/I71lpJwpyw/HzzK0vcEbthlOW4QVuw==", + "dependencies": { + "@rokucommunity/bslib": "^0.1.1", + "@rokucommunity/logger": "^0.3.9", + "@xml-tools/parser": "^1.0.7", + "array-flat-polyfill": "^1.0.1", + "chalk": "^2.4.2", + "chevrotain": "^7.0.1", + "chokidar": "^3.5.1", + "clear": "^0.1.0", + "cross-platform-clear-console": "^2.3.0", + "debounce-promise": "^3.1.0", + "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.12", + "file-url": "^3.0.0", + "fs-extra": "^8.1.0", + "jsonc-parser": "^2.3.0", + "long": "^3.2.0", + "luxon": "^2.5.2", + "minimatch": "^3.0.4", + "moment": "^2.23.0", + "p-settle": "^2.1.0", + "parse-ms": "^2.1.0", + "readline": "^1.3.0", + "require-relative": "^0.8.7", + "roku-deploy": "^3.12.2", + "serialize-error": "^7.0.1", + "source-map": "^0.7.4", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8", + "xml2js": "^0.5.0", + "yargs": "^16.2.0" + }, + "bin": { + "bsc": "dist/cli.js" + } + }, + "node_modules/roku-debug/node_modules/brighterscript/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/roku-debug/node_modules/brighterscript/node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/roku-debug/node_modules/brighterscript/node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, "node_modules/roku-debug/node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -8472,10 +8556,11 @@ } }, "node_modules/roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "dependencies": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -8488,7 +8573,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -8618,6 +8703,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, "dependencies": { "type-fest": "^0.13.1" }, @@ -10764,9 +10850,9 @@ } }, "@postman/tunnel-agent": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", - "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz", + "integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -11509,9 +11595,10 @@ } }, "brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.67.4", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.4.tgz", + "integrity": "sha512-1EbX2Po9WbIyi4r557fk6txkYR0VvbnGm1caS9AVlVek2R6d9+QsQxr+f24PePM4l/TOBvZ1JieBt6NheM4P2Q==", + "dev": true, "requires": { "@rokucommunity/bslib": "^0.1.1", "@rokucommunity/logger": "^0.3.9", @@ -11552,6 +11639,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -11562,6 +11650,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dev": true, "requires": { "vscode-languageserver-protocol": "3.17.5" } @@ -15085,13 +15174,13 @@ } }, "postman-request": { - "version": "2.88.1-postman.33", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", - "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "requires": { "@postman/form-data": "~3.1.1", "@postman/tough-cookie": "~4.1.3-postman.1", - "@postman/tunnel-agent": "^0.6.3", + "@postman/tunnel-agent": "^0.6.4", "aws-sign2": "~0.7.0", "aws4": "^1.12.0", "brotli": "^1.3.3", @@ -15964,6 +16053,74 @@ "xml2js": "^0.5.0" }, "dependencies": { + "brighterscript": { + "version": "0.67.8", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.8.tgz", + "integrity": "sha512-2pQcPzW/NCEEofojLuHRDcLuQTmHssNpj1xW2oRiSFMo0YHR5cBIHrG/I71lpJwpyw/HzzK0vcEbthlOW4QVuw==", + "requires": { + "@rokucommunity/bslib": "^0.1.1", + "@rokucommunity/logger": "^0.3.9", + "@xml-tools/parser": "^1.0.7", + "array-flat-polyfill": "^1.0.1", + "chalk": "^2.4.2", + "chevrotain": "^7.0.1", + "chokidar": "^3.5.1", + "clear": "^0.1.0", + "cross-platform-clear-console": "^2.3.0", + "debounce-promise": "^3.1.0", + "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.12", + "file-url": "^3.0.0", + "fs-extra": "^8.1.0", + "jsonc-parser": "^2.3.0", + "long": "^3.2.0", + "luxon": "^2.5.2", + "minimatch": "^3.0.4", + "moment": "^2.23.0", + "p-settle": "^2.1.0", + "parse-ms": "^2.1.0", + "readline": "^1.3.0", + "require-relative": "^0.8.7", + "roku-deploy": "^3.12.2", + "serialize-error": "^7.0.1", + "source-map": "^0.7.4", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8", + "xml2js": "^0.5.0", + "yargs": "^16.2.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "requires": { + "type-fest": "^0.13.1" + } + }, + "vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "requires": { + "vscode-languageserver-protocol": "3.17.5" + } + } + } + }, "dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -15992,10 +16149,11 @@ } }, "roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "requires": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -16008,7 +16166,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -16101,6 +16259,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, "requires": { "type-fest": "^0.13.1" } diff --git a/bsc-plugin/src/cli.ts b/bsc-plugin/src/cli.ts index ce3bbc69..38bd8c91 100644 --- a/bsc-plugin/src/cli.ts +++ b/bsc-plugin/src/cli.ts @@ -65,8 +65,8 @@ async function main() { await telnet.activate(); await telnet.connect(); - const failRegex = /RESULT: Fail/g; - const endRegex = /\[END TEST REPORT\]/g; + const failRegex = /\[Rooibos Result\]: (FAIL|PASS)/g; + const endRegex = /\[Rooibos Shutdown\]/g; async function doExit(emitAppExit = false) { if (emitAppExit) { @@ -82,7 +82,9 @@ async function main() { //check for Fails or Crashes let failMatches = failRegex.exec(output); if (failMatches && failMatches.length > 0) { - currentErrorCode = 1; + if (failMatches[1] === 'FAIL') { + currentErrorCode = 1; + } } let endMatches = endRegex.exec(output); diff --git a/bsc-plugin/src/lib/rooibos/Annotation.ts b/bsc-plugin/src/lib/rooibos/Annotation.ts index dfd3821e..5d2cffbe 100644 --- a/bsc-plugin/src/lib/rooibos/Annotation.ts +++ b/bsc-plugin/src/lib/rooibos/Annotation.ts @@ -1,5 +1,5 @@ import type { AnnotationExpression, BrsFile, Statement } from 'brighterscript'; -import { diagnosticIllegalParams, diagnosticNoTestNameDefined, diagnosticMultipleDescribeAnnotations, diagnosticMultipleTestOnFunctionDefined } from '../utils/Diagnostics'; +import { diagnosticIllegalParams, diagnosticNoTestNameDefined, diagnosticMultipleDescribeAnnotations, diagnosticMultipleTestOnFunctionDefined, diagnosticSlowAnnotationRequiresNumber } from '../utils/Diagnostics'; export enum AnnotationType { None = 'none', @@ -9,6 +9,7 @@ export enum AnnotationType { Ignore = 'ignore', Solo = 'only', NodeTest = 'sgnode', + Slow = 'slow', Setup = 'setup', TearDown = 'teardown', BeforeEach = 'beforeeach', @@ -30,6 +31,7 @@ let annotationLookup = { only: AnnotationType.Solo, sgnode: AnnotationType.NodeTest, setup: AnnotationType.Setup, + slow: AnnotationType.Slow, teardown: AnnotationType.TearDown, beforeeach: AnnotationType.BeforeEach, aftereach: AnnotationType.AfterEach, @@ -47,6 +49,8 @@ interface ParsedComment { testAnnotation?: RooibosAnnotation; } +const defaultSlowDuration = 75; + export class AnnotationParams { constructor( @@ -82,7 +86,8 @@ export class RooibosAnnotation { public nodeName?: string, rawTags: string[] = [], public noCatch = false, - public noEarlyExit = false + public noEarlyExit = false, + public slow = defaultSlowDuration ) { this.tags = new Set(rawTags); } @@ -192,6 +197,20 @@ export class RooibosAnnotation { //error } } + + for (const annotation of getAnnotationsOfType(AnnotationType.Slow)) { + if (testAnnotation) { + let slow = annotation.getArguments().length === 1 ? parseInt(annotation.getArguments()[0] as any) : defaultSlowDuration; + if (isNaN(slow)) { + diagnosticSlowAnnotationRequiresNumber(file, annotation); + testAnnotation.slow = defaultSlowDuration; + } else { + testAnnotation.slow = slow; + } + } else { + //error + } + } } } return { blockAnnotation: blockAnnotation, testAnnotation: testAnnotation }; diff --git a/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts b/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts index 1a038d3d..19bce2e5 100644 --- a/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts +++ b/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts @@ -67,8 +67,8 @@ describe('RooibosPlugin', () => { it('adds code coverage to a brs file', async () => { program.setFile('source/code.brs', ` function new(a1, a2) - c = 0 - text = "" + c = 0 + text = "" for i = 0 to 10 text = text + "hello" c++ diff --git a/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts b/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts index b0a48488..09a756db 100644 --- a/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts +++ b/bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts @@ -56,6 +56,7 @@ export class CodeCoverageProcessor { private coverageMap: Map; private fileFactory: FileFactory; private processedStatements: Set; + private addedStatements: Set; private astEditor: Editor; public generateMetadata(isUsingCoverage: boolean, program: Program) { @@ -74,6 +75,7 @@ export class CodeCoverageProcessor { this.coverageMap = new Map(); this.executableLines = new Map(); this.processedStatements = new Set(); + this.addedStatements = new Set(); this.astEditor = astEditor; file.ast.walk(createVisitor({ @@ -167,7 +169,7 @@ export class CodeCoverageProcessor { } private convertStatementToCoverageStatement(statement: Statement, coverageType: CodeCoverageLineType, owner: any, key: any) { - if (this.processedStatements.has(statement)) { + if (this.processedStatements.has(statement) || this.addedStatements.has(statement)) { return; } @@ -175,6 +177,7 @@ export class CodeCoverageProcessor { this.coverageMap.set(lineNumber, coverageType); const parsed = Parser.parse(this.getFuncCallText(lineNumber, coverageType)).ast.statements[0] as ExpressionStatement; this.astEditor.arraySplice(owner, key, 0, parsed); + this.addedStatements.add(parsed); // store the statement in a set to avoid handling again after inserting statement above this.processedStatements.add(statement); } @@ -182,6 +185,9 @@ export class CodeCoverageProcessor { public addBrsAPIText(file: BrsFile, astEditor: Editor) { const astCodeToInject = Parser.parse(this.coverageBrsTemplate.replace(/\#ID\#/g, this.fileId.toString().trim())).ast.statements; astEditor.arrayPush(file.ast.statements, ...astCodeToInject); + for (let statement of astCodeToInject) { + this.addedStatements.add(statement); + } } private addStatement(statement: Statement, lineNumber?: number) { diff --git a/bsc-plugin/src/lib/rooibos/RawCodeExpression.ts b/bsc-plugin/src/lib/rooibos/RawCodeExpression.ts index 82f0b6a5..4732f3d6 100644 --- a/bsc-plugin/src/lib/rooibos/RawCodeExpression.ts +++ b/bsc-plugin/src/lib/rooibos/RawCodeExpression.ts @@ -16,6 +16,10 @@ export class RawCodeExpression extends Expression { super(); } + public clone() { + return new RawCodeExpression(this.source, this.sourceFile, this.range); + } + public transpile(state: BrsTranspileState) { return [new SourceNode( this.range.start.line + 1, diff --git a/bsc-plugin/src/lib/rooibos/RawCodeStatement.ts b/bsc-plugin/src/lib/rooibos/RawCodeStatement.ts index 41ad22ca..4005af13 100644 --- a/bsc-plugin/src/lib/rooibos/RawCodeStatement.ts +++ b/bsc-plugin/src/lib/rooibos/RawCodeStatement.ts @@ -21,6 +21,10 @@ export class RawCodeStatement extends Statement { super(); } + public clone() { + return new RawCodeStatement(this.source, this.sourceFile, this.range); + } + public transpile(state: BrsTranspileState) { //indent every line with the current transpile indent level (except the first line, because that's pre-indented by bsc) let source = this.source.replace(/\r?\n/g, (match, newline) => { diff --git a/bsc-plugin/src/lib/rooibos/RooibosConfig.ts b/bsc-plugin/src/lib/rooibos/RooibosConfig.ts index 48d0814b..a23df1ee 100644 --- a/bsc-plugin/src/lib/rooibos/RooibosConfig.ts +++ b/bsc-plugin/src/lib/rooibos/RooibosConfig.ts @@ -22,6 +22,7 @@ export interface RooibosConfig { includeFilters?: string[]; tags?: string[]; catchCrashes?: boolean; + colorizeOutput?: boolean; throwOnFailedAssertion?: boolean; sendHomeOnFinish?: boolean; diff --git a/bsc-plugin/src/lib/rooibos/RooibosSession.ts b/bsc-plugin/src/lib/rooibos/RooibosSession.ts index ac5608e0..fa2422ba 100644 --- a/bsc-plugin/src/lib/rooibos/RooibosSession.ts +++ b/bsc-plugin/src/lib/rooibos/RooibosSession.ts @@ -146,6 +146,7 @@ export class RooibosSession { "printLcov": ${this.config.printLcov ? 'true' : 'false'} "port": "${this.config.port || 'invalid'}" "catchCrashes": ${this.config.catchCrashes ? 'true' : 'false'} + "colorizeOutput": ${this.config.colorizeOutput ? 'true' : 'false'} "throwOnFailedAssertion": ${this.config.throwOnFailedAssertion ? 'true' : 'false'} "keepAppOpen": ${this.config.keepAppOpen === undefined || this.config.keepAppOpen ? 'true' : 'false'} "isRecordingCodeCoverage": ${this.config.isRecordingCodeCoverage ? 'true' : 'false'} @@ -174,6 +175,7 @@ export class RooibosSession { switch (name.toLowerCase()) { case 'console': return 'rooibos_ConsoleTestReporter'; case 'junit': return 'rooibos_JUnitTestReporter'; + case 'mocha': return 'rooibos_MochaTestReporter'; } // @todo: check if function name is valid return name; diff --git a/bsc-plugin/src/lib/rooibos/RooibosSessionInfo.ts b/bsc-plugin/src/lib/rooibos/RooibosSessionInfo.ts index 558f8944..d6d50142 100644 --- a/bsc-plugin/src/lib/rooibos/RooibosSessionInfo.ts +++ b/bsc-plugin/src/lib/rooibos/RooibosSessionInfo.ts @@ -77,7 +77,7 @@ export class SessionInfo { } else if (this.hasSoloSuites && !testSuite.isSolo) { testSuite.isIncluded = false; } else if (testSuite.isIgnored) { - testSuite.isIncluded = false; + testSuite.isIncluded = true; this.ignoredTestNames.push(testSuite.name + ' [WHOLE SUITE]'); this.ignoredCount++; } else { @@ -88,62 +88,73 @@ export class SessionInfo { } //'testSuite ' + testSuite.name); for (let testGroup of testSuite.getTestGroups()) { + if (testSuite.isIgnored) { + testGroup.isIgnored = true; + } //'GROUP ' + testGroup.name); if (testGroup.isIgnored) { this.ignoredCount += testGroup.ignoredTestCases.length; this.ignoredTestNames.push(testGroup.name + ' [WHOLE GROUP]'); - testGroup.isIncluded = false; - } else { - if (testGroup.ignoredTestCases.length > 0) { - this.ignoredTestNames.push(testGroup.name); - this.ignoredCount += testGroup.ignoredTestCases.length; - for (let ignoredTestCase of testGroup.ignoredTestCases) { - if (!ignoredTestCase.isParamTest) { - this.ignoredTestNames.push(ignoredTestCase.name); - } else if (ignoredTestCase.paramTestIndex === 0) { - let testCaseName = ignoredTestCase.name; - if (testCaseName.length > 1 && testCaseName.substr(testCaseName.length - 1) === '0') { - testCaseName = testCaseName.substr(0, testCaseName.length - 1); - } - this.ignoredTestNames.push(testCaseName); + testGroup.isIncluded = true; + } + + if (testGroup.ignoredTestCases.length > 0) { + this.ignoredTestNames.push(testGroup.name); + this.ignoredCount += testGroup.ignoredTestCases.length; + for (let ignoredTestCase of testGroup.ignoredTestCases) { + if (!ignoredTestCase.isParamTest) { + this.ignoredTestNames.push(ignoredTestCase.name); + } else if (ignoredTestCase.paramTestIndex === 0) { + let testCaseName = ignoredTestCase.name; + if (testCaseName.length > 1 && testCaseName.substr(testCaseName.length - 1) === '0') { + testCaseName = testCaseName.substr(0, testCaseName.length - 1); } + this.ignoredTestNames.push(testCaseName); } } - if (this.isExcludedByTag(testGroup, true)) { - testGroup.isIncluded = false; - } else if (this.hasSoloTests && !testGroup.hasSoloTests) { - testGroup.isIncluded = false; - } else if (this.hasSoloGroups && !testGroup.isSolo) { - testGroup.isIncluded = false; - } else { - testGroup.isIncluded = true; + } + if (this.isExcludedByTag(testGroup, true)) { + testGroup.isIncluded = false; + } else if (this.hasSoloTests && !testGroup.hasSoloTests) { + testGroup.isIncluded = false; + } else if (this.hasSoloGroups && !testGroup.isSolo) { + testGroup.isIncluded = false; + } else { + testGroup.isIncluded = true; + } + + if (testGroup.isIncluded) { + this.groupsCount++; + let testCases = [...testGroup.testCases.values()]; + + for (let testCase of testCases) { + if (testGroup.isIgnored) { + testCase.isIgnored = true; + } + + if (this.isExcludedByTag(testCase, true)) { + testCase.isIncluded = false; + } else if (testCase.isIgnored) { + testCase.isIncluded = true; + } else if (this.hasSoloTests && !testCase.isSolo) { + testCase.isIncluded = false; + } else { + testCase.isIncluded = testGroup.isIncluded || testCase.isSolo; + } } - if (testGroup.isIncluded) { - this.groupsCount++; - let testCases = [...testGroup.testCases.values()]; - - for (let testCase of testCases) { - if (this.isExcludedByTag(testCase, true)) { - testCase.isIncluded = false; - } else if (testCase.isIgnored) { - testCase.isIncluded = false; - } else if (this.hasSoloTests && !testCase.isSolo) { - testCase.isIncluded = false; - } else { - testCase.isIncluded = testGroup.isIncluded || testCase.isSolo; - this.testsCount++; - } + for (let testCase of testGroup.soloTestCases) { + if (this.isExcludedByTag(testCase, true)) { + testCase.isIncluded = false; + } else { + testCase.isIncluded = true; } + } - for (let testCase of testGroup.soloTestCases) { - if (this.isExcludedByTag(testCase, true)) { - testCase.isIncluded = false; - } else { - testCase.isIncluded = true; - this.testsCount++; - } + for (let testCase of testCases) { + if (testCase.isIncluded) { + this.testsCount++; } } } diff --git a/bsc-plugin/src/lib/rooibos/TestCase.ts b/bsc-plugin/src/lib/rooibos/TestCase.ts index c0981228..076102a0 100644 --- a/bsc-plugin/src/lib/rooibos/TestCase.ts +++ b/bsc-plugin/src/lib/rooibos/TestCase.ts @@ -8,13 +8,13 @@ export class TestCase { this.isSolo = isSolo; this.isAsync = annotation.isAsync; this.asyncTimeout = annotation.asyncTimeout; + this.slow = annotation.slow; this.funcName = funcName; this.isIgnored = isIgnored; this.name = name; this.lineNumber = lineNumber; this.paramLineNumber = paramLineNumber; this.assertIndex = 0; - this.assertLineNumberMap = {}; this.rawParams = params; this.expectedNumberOfParams = expectedNumberOfParams; this.paramTestIndex = paramTestIndex; @@ -30,12 +30,12 @@ export class TestCase { public isParamTest: boolean; public isAsync: boolean; public asyncTimeout: number; + public slow: number; public name: string; public lineNumber: number; public paramLineNumber: number; public assertIndex: number; public expectedNumberOfParams: number; - public assertLineNumberMap: any; public rawParams: any[]; public paramTestIndex: number; @@ -50,12 +50,12 @@ export class TestCase { isIgnored: ${this.isIgnored} isAsync: ${this.isAsync} asyncTimeout: ${this.asyncTimeout || 2000} + slow: ${this.slow} isParamTest: ${this.isParamTest} name: ${sanitizeBsJsonString(this.name)} - lineNumber: ${this.lineNumber + 2} - paramLineNumber: ${this.paramLineNumber} + lineNumber: ${this.lineNumber + 1} + paramLineNumber: ${this.isParamTest ? this.paramLineNumber + 1 : 0} assertIndex: ${this.assertIndex} - assertLineNumberMap: ${JSON.stringify(this.assertLineNumberMap)} rawParams: ${rawParamsText} paramTestIndex: ${this.paramTestIndex} expectedNumberOfParams: ${this.expectedNumberOfParams} @@ -63,11 +63,6 @@ export class TestCase { }`; } - public addAssertLine(lineNumber: number) { - this.assertLineNumberMap[this.assertIndex.toString().trim()] = lineNumber; - this.assertIndex++; - } - fixBadJson(o) { // In case of an array we'll stringify all objects. if (Array.isArray(o)) { diff --git a/bsc-plugin/src/lib/rooibos/TestGroup.ts b/bsc-plugin/src/lib/rooibos/TestGroup.ts index 71df26f5..ee4e9ce2 100644 --- a/bsc-plugin/src/lib/rooibos/TestGroup.ts +++ b/bsc-plugin/src/lib/rooibos/TestGroup.ts @@ -32,6 +32,7 @@ export class TestGroup extends TestBlock { if (testCase.isIgnored) { this.ignoredTestCases.push(testCase); this.hasIgnoredTests = true; + this.soloTestCases.push(testCase); } else if (testCase.isSolo) { this.hasSoloTests = true; this.soloTestCases.push(testCase); @@ -78,7 +79,7 @@ export class TestGroup extends TestBlock { const trailingLine = Parser.parse(`if m.currentResult?.isFail = true then m.done() : return ${isSub ? '' : 'invalid'}`).ast.statements[0]; editor.arraySplice(owner, key + 1, 0, trailingLine); } - const leadingLine = Parser.parse(`m.currentAssertLineNumber = ${callExpression.range.start.line}`).ast.statements[0]; + const leadingLine = Parser.parse(`m.currentAssertLineNumber = ${callExpression.range.start.line + 1}`).ast.statements[0]; editor.arraySplice(owner, key, 0, leadingLine); } } @@ -220,7 +221,7 @@ export class TestGroup extends TestBlock { isSolo: ${this.isSolo} isIgnored: ${this.isIgnored} filename: "${this.pkgPath}" - lineNumber: "${this.annotation.annotation.range.start.line}" + lineNumber: "${this.annotation.annotation.range.start.line + 1}" setupFunctionName: "${this.setupFunctionName || ''}" tearDownFunctionName: "${this.tearDownFunctionName || ''}" beforeEachFunctionName: "${this.beforeEachFunctionName || ''}" diff --git a/bsc-plugin/src/lib/rooibos/TestSuite.ts b/bsc-plugin/src/lib/rooibos/TestSuite.ts index 076dc6b5..befc6e32 100644 --- a/bsc-plugin/src/lib/rooibos/TestSuite.ts +++ b/bsc-plugin/src/lib/rooibos/TestSuite.ts @@ -47,6 +47,10 @@ export class TestBlock { return this.annotation.isIgnore; } + public set isIgnored(value: boolean) { + this.annotation.isIgnore = value; + } + public isValid = false; public isIncluded = false; diff --git a/bsc-plugin/src/lib/utils/Diagnostics.ts b/bsc-plugin/src/lib/utils/Diagnostics.ts index e967b7e7..4b5db9b0 100644 --- a/bsc-plugin/src/lib/utils/Diagnostics.ts +++ b/bsc-plugin/src/lib/utils/Diagnostics.ts @@ -263,3 +263,12 @@ export function diagnosticNoStagingDir(file: BscFile) { ); } +export function diagnosticSlowAnnotationRequiresNumber(file: BrsFile, annotation: AnnotationExpression) { + addDiagnosticForAnnotation( + file, + 2222, + `Value for @slow must be a number in milliseconds`, + annotation + ); +} + diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts index 51e4d997..2da0898b 100644 --- a/bsc-plugin/src/plugin.spec.ts +++ b/bsc-plugin/src/plugin.spec.ts @@ -171,13 +171,18 @@ describe('RooibosPlugin', () => { `); program.validate(); expect(program.getDiagnostics()).to.be.empty; - expect(plugin.session.sessionInfo.testSuitesToRun).to.be.empty; + expect(plugin.session.sessionInfo.testSuitesToRun.length).to.be.equal(1); + expect(plugin.session.sessionInfo.groupsCount).to.equal(1); + expect(plugin.session.sessionInfo.testsCount).to.equal(1); + expect([...plugin.session.sessionInfo.testSuites.entries()][0][1].isIgnored).to.equal(true); + expect([...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].isIgnored).to.equal(true); + expect([...[...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].testCases.entries()][0][1].isIgnored).to.equal(true); }); it('ignores a group', () => { program.setFile('source/test.spec.bs', ` - @suite - class ATest + @suite + class ATest @ignore @describe("groupA") @@ -189,8 +194,12 @@ describe('RooibosPlugin', () => { `); program.validate(); expect(program.getDiagnostics()).to.be.empty; - expect(plugin.session.sessionInfo.groupsCount).to.equal(0); - expect(plugin.session.sessionInfo.testsCount).to.equal(0); + expect(plugin.session.sessionInfo.testSuitesToRun.length).to.be.equal(1); + expect(plugin.session.sessionInfo.groupsCount).to.equal(1); + expect(plugin.session.sessionInfo.testsCount).to.equal(1); + expect([...plugin.session.sessionInfo.testSuites.entries()][0][1].isIgnored).to.equal(false); + expect([...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].isIgnored).to.equal(true); + expect([...[...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].testCases.entries()][0][1].isIgnored).to.equal(true); }); it('ignores a test', () => { @@ -206,10 +215,15 @@ describe('RooibosPlugin', () => { end class `); + program.validate(); expect(program.getDiagnostics()).to.be.empty; + expect(plugin.session.sessionInfo.testSuitesToRun.length).to.be.equal(1); expect(plugin.session.sessionInfo.groupsCount).to.equal(1); - expect(plugin.session.sessionInfo.testsCount).to.equal(0); + expect(plugin.session.sessionInfo.testsCount).to.equal(1); + expect([...plugin.session.sessionInfo.testSuites.entries()][0][1].isIgnored).to.equal(false); + expect([...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].isIgnored).to.equal(false); + expect([...[...[...plugin.session.sessionInfo.testSuites.entries()][0][1].testGroups.entries()][0][1].testCases.entries()][0][1].isIgnored).to.equal(true); }); it('multiple groups', () => { @@ -434,6 +448,7 @@ describe('RooibosPlugin', () => { class ATest extends rooibos.BaseTestSuite @describe("groupA") @it("is test1") + @slow(1000) function Test_3() end function end class @@ -496,7 +511,7 @@ describe('RooibosPlugin', () => { isSolo: false isIgnored: false filename: "${s`source/test.spec.bs`}" - lineNumber: "3" + lineNumber: "4" setupFunctionName: "" tearDownFunctionName: "" beforeEachFunctionName: "" @@ -509,12 +524,12 @@ describe('RooibosPlugin', () => { isIgnored: false isAsync: false asyncTimeout: 2000 + slow: 1000 isParamTest: false name: "is test1" lineNumber: 7 paramLineNumber: 0 assertIndex: 0 - assertLineNumberMap: {} rawParams: invalid paramTestIndex: 0 expectedNumberOfParams: 0 @@ -654,7 +669,7 @@ describe('RooibosPlugin', () => { expect( testContents ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectCalled(m.thing, "callFunc", m, "m.thing", [ "getFunction" ]) @@ -662,7 +677,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(m.thing, "callFunc", m, "m.thing", [ "getFunction" ], "return") @@ -670,7 +685,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(m.thing, "callFunc", m, "m.thing", [ "getFunction" "a" @@ -680,7 +695,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(m.thing, "callFunc", m, "m.thing", [ "getFunction" "a" @@ -712,13 +727,13 @@ describe('RooibosPlugin', () => { expect( getTestFunctionContents() ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectCalled(m.thing, "getFunctionField", m, "m.thing", invalid) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(m.thing, "getFunctionField", m, "m.thing", invalid, "return") if m.currentResult?.isFail = true then m.done() @@ -749,19 +764,19 @@ describe('RooibosPlugin', () => { expect( testContents ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectCalled(m.thing, "getFunction", m, "m.thing", []) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -770,7 +785,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -802,19 +817,19 @@ describe('RooibosPlugin', () => { expect( getTestSubContents() ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectCalled(m.thing, "getFunction", m, "m.thing", []) if m.currentResult?.isFail = true then m.done() return end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return") if m.currentResult?.isFail = true then m.done() return end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -823,7 +838,7 @@ describe('RooibosPlugin', () => { m.done() return end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -857,19 +872,19 @@ describe('RooibosPlugin', () => { expect( testContents ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectCalled(m.thing, "getFunction", m, "m.thing", []) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(m.thing, "getFunction", m, "m.thing", [], "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -878,7 +893,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(m.thing, "getFunction", m, "m.thing", [ "arg1" "arg2" @@ -918,7 +933,7 @@ describe('RooibosPlugin', () => { b = { someValue: "value" } - m.currentAssertLineNumber = 12 + m.currentAssertLineNumber = 13 m.assertEqual(b, { someValue: "value" }) @@ -955,19 +970,19 @@ describe('RooibosPlugin', () => { item = { id: "item" } - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(item, "getFunction", item, "item", []) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(item, "getFunction", item, "item", [], "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(item, "getFunction", item, "item", [ "arg1" "arg2" @@ -976,7 +991,7 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 10 + m.currentAssertLineNumber = 11 m._expectCalled(item, "getFunction", item, "item", [ "arg1" "arg2" @@ -1019,7 +1034,7 @@ describe('RooibosPlugin', () => { item = { id: "item" } - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(sayHello, "sayHello", invalid, invalid, [ "arg1" "arg2" @@ -1028,19 +1043,19 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(sayHello, "sayHello", invalid, invalid, []) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(sayHello, "sayHello", invalid, invalid, [], "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 10 + m.currentAssertLineNumber = 11 m._expectCalled(sayHello, "sayHello", invalid, invalid, [ "arg1" "arg2" @@ -1110,7 +1125,7 @@ describe('RooibosPlugin', () => { item = { id: "item" } - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [ "arg1" "arg2" @@ -1119,19 +1134,19 @@ describe('RooibosPlugin', () => { m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, []) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [], "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 10 + m.currentAssertLineNumber = 11 m._expectCalled(utils_sayhello, "utils_sayhello", invalid, invalid, [ "arg1" "arg2" @@ -1348,19 +1363,19 @@ describe('RooibosPlugin', () => { m.wasCalled = true return true end function) - m.currentAssertLineNumber = 12 + m.currentAssertLineNumber = 13 m.assertTrue(globalFunctionWithReturn()) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 13 + m.currentAssertLineNumber = 14 m.assertTrue(getGlobalAA().wasCalled) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 14 + m.currentAssertLineNumber = 15 m.assertRunningTestIsPassed() if m.currentResult?.isFail = true then m.done() @@ -1408,19 +1423,19 @@ describe('RooibosPlugin', () => { m.wasCalled = true return true end function) - m.currentAssertLineNumber = 12 + m.currentAssertLineNumber = 13 m.assertTrue(testNamespace_functionWithReturn()) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 13 + m.currentAssertLineNumber = 14 m.assertTrue(getGlobalAA().wasCalled) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 14 + m.currentAssertLineNumber = 15 m.assertRunningTestIsPassed() if m.currentResult?.isFail = true then m.done() @@ -1470,19 +1485,19 @@ describe('RooibosPlugin', () => { return true end function m.stubCall(testNamespace_functionWithReturn, stub) - m.currentAssertLineNumber = 13 + m.currentAssertLineNumber = 14 m.assertTrue(testNamespace_functionWithReturn()) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 14 + m.currentAssertLineNumber = 15 m.assertTrue(getGlobalAA().wasCalled) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 15 + m.currentAssertLineNumber = 16 m.assertRunningTestIsPassed() if m.currentResult?.isFail = true then m.done() @@ -1530,19 +1545,19 @@ describe('RooibosPlugin', () => { m.wasCalled = true return true end function) - m.currentAssertLineNumber = 12 + m.currentAssertLineNumber = 13 m.assertTrue(testNamespace_functionWithReturn()) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 13 + m.currentAssertLineNumber = 14 m.assertTrue(getGlobalAA().wasCalled) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 14 + m.currentAssertLineNumber = 15 m.assertRunningTestIsPassed() if m.currentResult?.isFail = true then m.done() @@ -1592,19 +1607,19 @@ describe('RooibosPlugin', () => { return true end function m.stubCall(testNamespace_functionWithReturn, stub) - m.currentAssertLineNumber = 13 + m.currentAssertLineNumber = 14 m.assertTrue(testNamespace_functionWithReturn()) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 14 + m.currentAssertLineNumber = 15 m.assertTrue(getGlobalAA().wasCalled) if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 15 + m.currentAssertLineNumber = 16 m.assertRunningTestIsPassed() if m.currentResult?.isFail = true then m.done() @@ -1648,7 +1663,7 @@ describe('RooibosPlugin', () => { expect( getTestFunctionContents() ).to.eql(undent` - m.currentAssertLineNumber = 15 + m.currentAssertLineNumber = 16 m.assertArrayContainsOnlyValuesOfType(values, typeName) if m.currentResult?.isFail = true then m.done() @@ -1656,7 +1671,7 @@ describe('RooibosPlugin', () => { end if isFail = m.currentResult.isFail m.currentResult.Reset() - m.currentAssertLineNumber = 20 + m.currentAssertLineNumber = 21 m.assertFalse(isFail) if m.currentResult?.isFail = true then m.done() @@ -1688,25 +1703,25 @@ describe('RooibosPlugin', () => { expect( getTestFunctionContents() ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectNotCalled(m.thing, "callFunc", m, "m.thing") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectNotCalled(m.thing, "callFunc", m, "m.thing", "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectNotCalled(m.thing, "callFunc", m, "m.thing") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectNotCalled(m.thing, "callFunc", m, "m.thing", "return") if m.currentResult?.isFail = true then m.done() @@ -1736,13 +1751,13 @@ describe('RooibosPlugin', () => { getTestFunctionContents() ).to.eql(undent` thing = {} - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectNotCalled(thing, "callFunc", thing, "thing") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectNotCalled(thing, "callFunc", thing, "thing") if m.currentResult?.isFail = true then m.done() @@ -1782,7 +1797,7 @@ describe('RooibosPlugin', () => { expect( getTestFunctionContents() ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectNotCalled(m.thing, "getFunctionField", m, "m.thing") if m.currentResult?.isFail = true then m.done() @@ -1819,25 +1834,25 @@ describe('RooibosPlugin', () => { expect( getTestFunctionContents() ).to.eql(undent` - m.currentAssertLineNumber = 6 + m.currentAssertLineNumber = 7 m._expectNotCalled(m.thing, "getFunction", m, "m.thing") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectNotCalled(m.thing, "getFunction", m, "m.thing", "return") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectNotCalled(m.thing, "getFunction", m, "m.thing") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 9 + m.currentAssertLineNumber = 10 m._expectNotCalled(m.thing, "getFunction", m, "m.thing", "return") if m.currentResult?.isFail = true then m.done() @@ -1869,13 +1884,13 @@ describe('RooibosPlugin', () => { item = { id: "item" } - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectNotCalled(item, "getFunction", item, "item") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectNotCalled(item, "getFunction", item, "item") if m.currentResult?.isFail = true then m.done() @@ -2072,13 +2087,13 @@ describe('RooibosPlugin', () => { item = { id: "item" } - m.currentAssertLineNumber = 7 + m.currentAssertLineNumber = 8 m._expectNotCalled(item, "getFunction", item, "item") if m.currentResult?.isFail = true then m.done() return invalid end if - m.currentAssertLineNumber = 8 + m.currentAssertLineNumber = 9 m._expectNotCalled(item, "getFunction", item, "item") if m.currentResult?.isFail = true then m.done() @@ -2111,6 +2126,7 @@ describe('RooibosPlugin', () => { "printLcov": false "port": "invalid" "catchCrashes": true + "colorizeOutput": false "throwOnFailedAssertion": false "keepAppOpen": true "isRecordingCodeCoverage": false @@ -2155,6 +2171,7 @@ describe('RooibosPlugin', () => { [[], 'rooibos_ConsoleTestReporter'], [['CONSOLE'], 'rooibos_ConsoleTestReporter'], [['MyCustomReporter'], 'MyCustomReporter'], + [['mocha'], 'rooibos_MochaTestReporter'], [['JUnit', 'MyCustomReporter'], `rooibos_JUnitTestReporter${sep}MyCustomReporter`] ]; it('adds custom test reporters', async () => { @@ -2173,9 +2190,7 @@ describe('RooibosPlugin', () => { await builder.transpile(); - expect( - getContents('rooibos/RuntimeConfig.brs') - ).to.eql(undent` + let fullExpected = undent` function __rooibos_RuntimeConfig_builder() instance = {} instance.new = function() @@ -2198,6 +2213,7 @@ describe('RooibosPlugin', () => { "printLcov": false "port": "invalid" "catchCrashes": true + "colorizeOutput": false "throwOnFailedAssertion": false "keepAppOpen": true "isRecordingCodeCoverage": false @@ -2225,7 +2241,11 @@ describe('RooibosPlugin', () => { instance.new() return instance end function - `); + `; + + expect( + getContents('rooibos/RuntimeConfig.brs') + ).to.eql(fullExpected); destroyProgram(); } @@ -2250,6 +2270,7 @@ describe('RooibosPlugin', () => { 'rooibos': { 'showOnlyFailures': true, 'catchCrashes': true, + 'colorizeOutput': false, 'throwOnFailedAssertion': false, 'lineWidth': 70, 'failFast': false, diff --git a/bsc-plugin/src/plugin.ts b/bsc-plugin/src/plugin.ts index 520d83ed..0685ade6 100644 --- a/bsc-plugin/src/plugin.ts +++ b/bsc-plugin/src/plugin.ts @@ -50,6 +50,9 @@ export class RooibosPlugin implements CompilerPlugin { if (config.catchCrashes === undefined) { config.catchCrashes = true; } + if (config.colorizeOutput === undefined) { + config.colorizeOutput = false; + } if (config.throwOnFailedAssertion === undefined) { config.throwOnFailedAssertion = false; } diff --git a/docs/index.md b/docs/index.md index 68af8436..2c8dbb50 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,7 +18,7 @@ Simple, mocha-inspired, flexible, fun Brightscript test framework for ROKU apps - [Node specific assertions](#node-specific-assertions) - [Parameterized testing](#parameterized-testing) - [Mocks and stubs](#mocks-and-stubs) - - [Execute tests on scenegraph nodes](#execute-tests-on-scenegraph-nodes) + - [Execute tests on SceneGraph nodes](#execute-tests-on-scenegraph-nodes) - [Incorporate your own util methods](#incorporate-your-own-util-methods) - [Hook into your global setup mechanisms](#hook-into-your-global-setup-mechanisms) - [Only show output for failed tests](#only-show-output-for-failed-tests) @@ -96,7 +96,7 @@ The easiest thing to do is to clone that project and - merge the contents of the files in .vscode folder - merge the contents of package.json into your project -You can do the setup from scratch as followes: +You can do the setup from scratch as follows: 1. Ensure your project is set up for use with npm (`npm init`, and follow the steps) 2. Install brighterscript: `npm install brighterscript --save-dev` @@ -165,6 +165,7 @@ Here is the information converted into a Markdown table: | printTestTimes? | boolean | If true, then the time each test took is output | | lineWidth? | number | Width of test output lines in columns | | catchCrashes? | boolean | If true, then any crashes will report CRASH statement, and note halt test execution - very useful for running a whole suite | +| colorizeOutput? | boolean | If true, reporters will add asci colorization to the console output if supported. (currently supported by the `mocha` reporter) | | throwOnFailedAssertion? | boolean | If true, then any failure will result in a runtime crash. Very useful for inspecting the stack frames and jumping right to the first failed test. | | sendHomeOnFinish? | boolean | If true, then the app will exit upon finish. The default is true. Useful to set to false for local test suites | | keepAppOpen? | boolean | When true, the app will remain open upon test completion. The default is true. Set false to return execution to Main | @@ -174,8 +175,8 @@ Here is the information converted into a Markdown table: | isGlobalMethodMockingEnabled | boolean | Default is false. Enables mocking and stubbing support for global and namespace functions | | isGlobalMethodMockingEfficientMode | boolean | default to true, when set causes rooibos to modify only those functions that were mocked or stubbed | | globalMethodMockingExcludedFiles | string[] | Files that rooibos will not modify when adding global function or namespace function mocking support | -| reporter? @deprecated 1 | string | The built-in reporter to use. Defaults to empty. Possible values are `console` and `junit`. | -| reporters? 2 | string[] | An array of factory functions/classes which implement `rooibos.BaseTestReporter`. Built-in reporters include `console` and `junit`. Defaults to `["console"]`. | +| reporter? @deprecated 1 | string | The built-in reporter to use. Defaults to empty. Possible values are `console`, `junit`, and `mocha`. | +| reporters? 2 | string[] | An array of factory functions/classes which implement `rooibos.BaseTestReporter`. Built-in reporters include `console`, `junit`, and `mocha`. Defaults to `["console"]`. | **1** This parameter is deprecated, use `reporters` instead. When specified, the reporter will be appended to the list of `reporters`. **2** Custom reporters are not currently supported on [node-based tests](#testing-nodes), because rooibos does not know which files it should include in the generated test components. This will be addressed in a future Rooibos version (see issue [#266](https://github.com/rokucommunity/rooibos/issues/266)). @@ -222,7 +223,7 @@ namespace tests function _() m.assertNotInvalid(m.node) Tests.doSomethingInNodeScope(true) - m.assertIonvalid(m._isNodeScopeVarSet) + m.assertInvalid(m._isNodeScopeVarSet) m.assertTrue(m.node._isNodeScopeVarSet) end function @@ -247,7 +248,7 @@ namespace tests sub _() m.assertNotInvalid(m.node) Tests.doSomethingInNodeScope(true) - m.assertIonvalid(m._isNodeScopeVarSet) + m.assertInvalid(m._isNodeScopeVarSet) m.assertTrue(m.node._isNodeScopeVarSet) end sub @@ -277,7 +278,7 @@ NOTE - these are official bsc compiler annotations; not like comments in the pre Where `ANNOTATION`, is the roku annotation and DATA is the data passed to it. e.g. `@it("that it handles an empty collection")`, defines a test case, with the title `that it handles an empty collection` -Some annotations act as modifiers. In these cases, they will affect some other annotation. For example `@only`, and `@ignore` will affect the following `@suite`, `@it` or `@it` annotation. +Some annotations act as modifiers. In these cases, they will affect some other annotation. For example `@only`, and `@ignore` will affect the following `@suite`, `@describe` or `@it` annotation. The following annotations are supported. @@ -293,13 +294,14 @@ The following annotations are supported. | @it | Indicates a test. Must directly precede a function definition | The name of the test case, which will be reported in the test output | | @only | Precedes a Suite, Describe group, or it test, to indicate that _only that item_ should be executed. This can be used to rapidly filter out tests. Only other `@only` items will be run. | | | @ignore | Precedes a suite, Describe group or it test, to indicate that that item should be ignored. If an `@ignore` tag is found before an item, then it will not be executed as part of the test run | | -| @params[p1,p2,...,p6] | Indicates a Parameterized test. Must come _after_ a `@it` annotation. Can accept up to 6 arguments, which are comma separated. When using parameterized tests, the test function signatrue _must_ accept the same number of arguments, and each of params statemens, must also have the same number of params | Up to 6 arguments can be any valid brightscript code, which can be parsed with an `eval` function | +| @params[p1,p2,...,p6] | Indicates a Parameterized test. Must come _after_ a `@it` annotation. Can accept up to 6 arguments, which are comma separated. When using parameterized tests, the test function signature _must_ accept the same number of arguments, and each of params statement, must also have the same number of params | Up to 6 arguments can be any valid brightscript code, which can be parsed with an `eval` function | | @ignoreParams[p1,p2,...,p6] | A Convenience tag, which makes it easy to temporarily _comment out_ params tests we do not want to run. | As per `@params` | | @onlyParams[p1,p2,...,p6] | A Convenience tag, which makes it easy to temporarily run just one set of params, so you can run one or more of the params in a params block. Very useful for focusing on a failing test case | As per `@params` | -| @tags("one","two"..."n") | Allows indicating the tags to apply to the group,test or suite. This is a really effective way to categorise your test suite. These tags can be used to filter tests in your rooibos bsconfig options. | List of tag names to apply | +| @tags("one","two"..."n") | Allows indicating the tags to apply to the group,test or suite. This is a really effective way to categorize your test suite. These tags can be used to filter tests in your rooibos bsconfig options. | List of tag names to apply | | @noCatch | If present, will not catch errors for the test or suite it is placed on. This is handy when developing, and you want to debug the exact line on which an error occurred. | none | -| @noEarlyexit | If present, will not exit a test on an assertion failure, which prevents crashes/skewed results. This annotation is mainly used for testing, such as testing rooibos framework itself. It is recommend that you _do not_ use this annotation. | none | +| @noEarlyExit | If present, will not exit a test on an assertion failure, which prevents crashes/skewed results. This annotation is mainly used for testing, such as testing rooibos framework itself. It is recommend that you _do not_ use this annotation. | none | | @async | If present, on a test suite or test case (e.g. @it) indicates that the test will execute asynchronously. This will allow you to use observeField in your tests. The only argument is timeout in ms e.g. @async(2000). Default time out is 2000ms for a test, and 6000 for a test suite | max time in ms | +| @slow | Some reporters will display test duration and flag tests that are slow (default: 75ms). This annotation lets you change the default for a single test. e.g. @slow(150) | slow time in ms | @@ -338,7 +340,7 @@ Rooibos provids many assertions to test your code with: - assertEqual - assertLike - assertNotEqual - - assertIonvalid + - assertInvalid - assertNotInvalid - assertAAHasKey - assertAANotHasKey @@ -390,7 +392,7 @@ This is useful in some scenarios, such as in maestro framework, where an object, ### Async tests -Rooibos runs in sync mode. Due to scenegraph limitations, we can't use observefield. We can workaround this though, using `assertAsyncField` +Rooibos runs in sync mode. Due to SceneGraph limitations, we can't use `observeField`. We can workaround this though, using `assertAsyncField` This assert allows you to wait on a field set on a task, or some other async manipulated object. Use as such: @@ -412,7 +414,7 @@ You can control the timeout behavior by passing delay and maxAttempts, as follow If the field does not change during the retry period, the assertion will fail. ### Setting up and tearing down -You may find that you have data which is common to all of your tests in a suite. In this case you can desginate functions to run, before and after **all** tests are executed in your suite. To achieve this, simply override the `setup` and `tearDown` functions. In our example above, we could do the following: +You may find that you have data which is common to all of your tests in a suite. In this case you can designate functions to run, before and after **all** tests are executed in your suite. To achieve this, simply override the `setup` and `tearDown` functions. In our example above, we could do the following: ``` override function setup() @@ -460,7 +462,7 @@ namespace Tests function _() item = m.alternateDS.GetDataItemWithIndex(12) - m.assertIonvalid(item) + m.assertInvalid(item) end function @@ -493,11 +495,11 @@ You can run the same test several times, by adding one or more `@params(...)` an @params(0, false) @params(1, false) @params("test", false) -function _(value, expectedassertResult) +function _(value, expectedAssertResult) ... ``` -In this case, the test will be run once for each of the `@params` annotations. Note that the method signatrue takes parameters which correspond to the arguments in the params arrays. Rooibos will give you a build time error, and diagnostic in the ide if you screw this up to save you scratching your head later. +In this case, the test will be run once for each of the `@params` annotations. Note that the method signature takes parameters which correspond to the arguments in the params arrays. Rooibos will give you a build time error, and diagnostic in the ide if you screw this up to save you scratching your head later. This makes it easy for us to pass in values to our tests, and expected output values, e.g. @@ -605,7 +607,7 @@ You can give a reason for ignoring a test, as part of the annotation's data. e.g function Simpl_Datastore_alternate_failures() item = m.alternateDS.GetDataItemWithIndex(12) - m.assertIonvalid(item) + m.assertInvalid(item) end function ``` @@ -614,12 +616,12 @@ The log reporter will indicate which tests are ignored, if you have log verbosit ### Only annotation If you place `@only` above a test suite, describe group, or test case, it will run that test in solo mode. In solo mode, execution is limited to those suites, groups or test cases, which also have a `@only' annotation. -A good working practice is to put a `@only` annotaiton on the suite for the class you are working on, then the group, then the individual test. You can then simply remove the annotation from the test when you have finished, and run the tests again, to see if you caused regression in any of the group's tests, then remove from the group and run the suite, then finally remove the `@only` annotation from the suite. This will allow you to run the least amount of tests at any time, while you work, giving you the fastest testing turnaround time. +A good working practice is to put a `@only` annotation on the suite for the class you are working on, then the group, then the individual test. You can then simply remove the annotation from the test when you have finished, and run the tests again, to see if you caused regression in any of the group's tests, then remove from the group and run the suite, then finally remove the `@only` annotation from the suite. This will allow you to run the least amount of tests at any time, while you work, giving you the fastest testing turnaround time. ### Only show output for failures -In addition to the the `@only` and `@ignore` annotations, Rooibos has another mechanism for aiding the TDD process. You are able to execute Rooibos in `showOnlyFailures` mode. In this mode, all tests are executed (according to the `@only` and `@ignore` annotations); but if any failures are encountered, then only the failures are displayed. If all tests pass, then the stanard test output is shown. +In addition to the the `@only` and `@ignore` annotations, Rooibos has another mechanism for aiding the TDD process. You are able to execute Rooibos in `showOnlyFailures` mode. In this mode, all tests are executed (according to the `@only` and `@ignore` annotations); but if any failures are encountered, then only the failures are displayed. If all tests pass, then the standard test output is shown. This makes it easy to quickly dive into the test suite and see what regressions have been introduced, then you can simply navigate to the failed tests and annotate them with `@only` annotations (so that subsequent runs are much quicker) @@ -686,10 +688,10 @@ m.assertFalse(detailsVM.isLoading) m.assertTrue(detailsVM.isShowingError) ``` -In this case, our detailsVM object, will not actually call executeNetRequests's source code; but will instead call a _fake_ (i.e fake method body), which can return predtermined values, or be later checked for invocation arg conformance. +In this case, our detailsVM object, will not actually call executeNetRequests's source code; but will instead call a _fake_ (i.e fake method body), which can return predetermined values, or be later checked for invocation arg conformance. #### Mocks -Mocks are _expected_ fakes. Your code will invoke the method, as if it _is_ the real method; but the difference is that Rooibos will track the invoction of mocks, and if the method was not invoked in the manner you expected (i.e. with the expected parameters and the expected number of invocations) then a unit test failure will result. +Mocks are _expected_ fakes. Your code will invoke the method, as if it _is_ the real method; but the difference is that Rooibos will track the invocation of mocks, and if the method was not invoked in the manner you expected (i.e. with the expected parameters and the expected number of invocations) then a unit test failure will result. We create mocks by using the methods: @@ -702,7 +704,7 @@ These are advanced functions, using the rooibos plugin to greatly simplify mocki - it will Create the underlying rooibos mock method for you - and will wire up the correct expected values and return values - it will automatically create the whole chain of objects required for the mock to work. For example, if you do - `m.expectCalled(m.screen.entitlementService.manager.isEntitled(), true)` and screen, entitlementServic or manager do not exist, rooibos will _automatically_ create the chain of objects as simple aa's with the relevant id, and setup the mock call for you. + `m.expectCalled(m.screen.entitlementService.manager.isEntitled(), true)` and screen, entitlementService or manager do not exist, rooibos will _automatically_ create the chain of objects as simple aa's with the relevant id, and setup the mock call for you. ### CallFunc @. nuances @@ -716,9 +718,9 @@ You can also `expectNotCalled` on both; but there is a slight difference here: `m.expectNotCalled(m.screen.entitlementService.manager@.isEntitled())` For the regular class method variation, you can simply pass a pointer to the function, for the @.variation you must use an empty params invocation (to satisfy the brighterscript transpiler), - #### Legacy mocking methods +#### Legacy mocking methods - Under the hood rooibos leverages these methods; but with the modern syntax you will not typicall interact with these methods. + Under the hood rooibos leverages these methods; but with the modern syntax you will not typically interact with these methods. - expect - Creates a generic mock - expectOnce - Creates a mock, which we expect to be called once _or can created individual overloaded calls to the same method_ diff --git a/framework/src/source/rooibos/BaseTestReporter.bs b/framework/src/source/rooibos/BaseTestReporter.bs index 15db20ef..7de71b1b 100644 --- a/framework/src/source/rooibos/BaseTestReporter.bs +++ b/framework/src/source/rooibos/BaseTestReporter.bs @@ -1,27 +1,87 @@ namespace rooibos - interface ITestReporterOnEndEvent - stats as rooibos.Stats - end interface - class BaseTestReporter public testRunner = invalid public config = invalid public allStats = invalid + public colorizeOutput = false - function new(runner as dynamic) + sub new(runner as dynamic) m.testRunner = runner m.config = runner.config m.allStats = runner.stats - end function + m.colorizeOutput = runner.config.colorizeOutput = true + end sub + + sub onBegin(event as rooibos.TestReporterOnBeginEvent) + 'override me + rooibos.common.logDebug("BaseTestReporter.onBegin") + end sub + + sub onSuiteBegin(event as rooibos.TestReporterOnSuiteBeginEvent) + 'override me + end sub + + sub onTestGroupBegin(event as rooibos.TestReporterOnTestGroupBeginEvent) + 'override me + end sub + + sub onTestBegin(event as rooibos.TestReporterOnTestBeginEvent) + 'override me + end sub + + sub onTestComplete(event as rooibos.TestReporterOnTestCompleteEvent) + 'override me + end sub - function onBegin(ev as dynamic) + sub onTestGroupComplete(event as rooibos.TestReporterOnTestGroupCompleteEvent) 'override me - end function + end sub - function onEnd(ev as rooibos.ITestReporterOnEndEvent) + sub onSuiteComplete(event as rooibos.TestReporterOnSuiteCompleteEvent) 'override me - end function + end sub + + sub onEnd(event as rooibos.TestReporterOnEndEvent) + 'override me + rooibos.common.logDebug("BaseTestReporter.onEnd") + end sub end class -end namespace \ No newline at end of file + + interface TestReporterOnBeginEvent + runner as rooibos.TestRunner + end interface + + interface TestReporterOnSuiteBeginEvent + suite as rooibos.BaseTestSuite + end interface + + interface TestReporterOnTestGroupBeginEvent + group as rooibos.TestGroup + end interface + + interface TestReporterOnTestBeginEvent + test as rooibos.Test + end interface + + interface TestReporterOnTestCompleteEvent + test as rooibos.Test + end interface + + interface TestReporterOnTestGroupCompleteEvent + group as rooibos.TestGroup + end interface + + interface TestReporterOnSuiteCompleteEvent + suite as rooibos.BaseTestSuite + end interface + + ' @depreciated use rooibos.TestReporterOnEndEvent + interface ITestReporterOnEndEvent + stats as rooibos.Stats + end interface + interface TestReporterOnEndEvent + stats as rooibos.Stats + end interface +end namespace diff --git a/framework/src/source/rooibos/BaseTestSuite.bs b/framework/src/source/rooibos/BaseTestSuite.bs index 87cf90eb..6db66780 100644 --- a/framework/src/source/rooibos/BaseTestSuite.bs +++ b/framework/src/source/rooibos/BaseTestSuite.bs @@ -2,7 +2,7 @@ namespace rooibos ' /** ' * @module TestSuite ' * @description All brs files that include `'@TestSuite` annotations automatically extend the TestSuite. - ' * The base test suite contains all of the assertions, and utility methods required to writey our tests, as well as being responsible for tracking the state of the tests and groups. + ' * The base test suite contains all of the assertions, and utility methods required to write our tests, as well as being responsible for tracking the state of the tests and groups. ' */ class BaseTestSuite @@ -59,7 +59,7 @@ namespace rooibos function new() data = m.getTestSuitedata() if data = invalid - ? "ERROR RETRIEVING TEST SUITE DATA!! this is a rooibos BUG - please report the suite that resulted in a corrupt test. Thanks" + rooibos.common.logError("ERROR RETRIEVING TEST SUITE DATA!! this is a rooibos BUG - please report the suite that resulted in a corrupt test. Thanks") else m.name = data.name m.filePath = data.filePath @@ -109,14 +109,14 @@ namespace rooibos '++++++++++++++++++++++++++++++++++++?+++++++++++++++++++++++++ function run() - - ' ? ">>>>>>>>>>>>" - ' ? " RUNNING TEST SUITE" + m.notifyReportersOnSuiteBegin() + rooibos.common.logTrace(">>>>>>>>>>>>") + rooibos.common.logTrace("RUNNING TEST SUITE") if m.isAsync = true - ' ? " THIS GROUP IS ASYNC" + rooibos.common.logTrace("THIS GROUP IS ASYNC") m.runAsync() else - ' ? " THIS GROUP IS SYNC" + rooibos.common.logTrace("THIS GROUP IS SYNC") m.runSync() end if end function @@ -125,11 +125,6 @@ namespace rooibos for each groupData in m.groupsData 'bs:disable-next-line group = new TestGroup(m, groupData) - ? "" - ? rooibos.common.fillText(">>>> Describe: " + group.name, ">", 80) - 'bs:disable-next-line - ? ` Location: file://${group.testSuite.filePath.trim()}:${group.lineNumber}` - ? "" 'bs:disable-next-line m.groups.push(group) @@ -140,7 +135,7 @@ namespace rooibos m.stats.merge(group.stats) if m.stats.hasFailures and m.isFailingFast = true - ? "Terminating suite due to failed group" + rooibos.common.logDebug("Terminating suite due to failed group") exit for end if @@ -154,10 +149,11 @@ namespace rooibos } }) end if + m.notifyReportersOnSuiteComplete() end function function runASync() - ' ? "running groups async" + rooibos.common.logTrace("Running groups async") m.groups = [] for each groupData in m.groupsData @@ -172,54 +168,47 @@ namespace rooibos end function private function runNextAsync() - ' ? "Getting next async group" + rooibos.common.logTrace("Getting next async group") m.currentGroupIndex++ m.currentGroup = m.groups[m.currentGroupIndex] if m.currentGroup = invalid m.setTestTimer(0) - ' ? " all groups are finished" + rooibos.common.logTrace("All groups are finished") 'finished m.finishAsyncGroups() else group = m.currentGroup m.testRunner.top.rooibosGroupFinished = false - ? "" - ? rooibos.common.fillText(">>>> Describe: " + group.name, ">", 80) - 'bs:disable-next-line - ? ` Location: file://${group.testSuite.filePath.trim()}:${group.lineNumber}` - ? "" - m.testRunner.top.observeFieldScoped("rooibosGroupFinished", "rooibos_onGroupComplete") - group.run() + completed = group.run() if group.stats.hasFailures - ? "Group failed before any async code could be executed" + rooibos.common.logDebug("Group failed before any async code could be executed") m.testRunner.top.unobserveFieldScoped("rooibosGroupFinished") ' m.testGroupDone() m.onAsyncGroupComplete(group) else if m.testRunner.top.rooibosGroupFinished m.onAsyncGroupComplete(group) end if - end if end function private function onAsyncGroupComplete(group = invalid) as void - ' ? "++ CURRENT GROUP COMPLETED" + rooibos.common.logTrace("++ CURRENT GROUP COMPLETED") m.testRunner.top.unobserveFieldScoped("rooibosGroupFinished") group = group = invalid ? m.currentGroup : group if group = invalid - ? "cannot find test group to mark async finished for?!" + rooibos.common.logError("Cannot find test group to mark async finished for?!") return end if 'bs:disable-next-line m.stats.merge(group.stats) if m.stats.hasFailures and m.isFailingFast - ' ? "Terminating group due to failed test" + rooibos.common.logTrace("Terminating group due to failed test") m.isTestFailedDueToEarlyExit = true m.finishAsyncGroups() else @@ -237,12 +226,12 @@ namespace rooibos groups: m.groups } }) - ' ? " indicating test suite is done" + rooibos.common.logTrace("Indicating test suite is done") m.testSuiteDone() end function private function setTestTimer(duration) - ' ? " SETTING TIMER FOR " duration + rooibos.common.logTrace(`SETTING TIMER FOR ${duration}`) if m.testTimer = invalid m.testTimer = createObject("roSGNode", "Timer") end if @@ -259,12 +248,11 @@ namespace rooibos end function function runTest(test as rooibos.Test) - ? "" - ? rooibos.common.fillText(">>>>>> It: " + test.name, ">", 80) - ? ` Location: file://${test.testSuite.filePath.trim()}:${test.lineNumber}` - ? "" - m.currentResult = test.result + if test.isIgnored + m.currentResult.skip("Test is ignored") + return invalid + end if m.currentResult.throwOnFailedAssertion = m.throwOnFailedAssertion if m.catchCrashes and not test.noCatch and not m.noCatch try @@ -286,9 +274,6 @@ namespace rooibos m.CleanStubs() end if end if - - ? rooibos.common.fillText("<<<< END It: " + test.name + " (" + m.currentResult.getStatusText() + ") ", "<", 80) - end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -304,14 +289,34 @@ namespace rooibos ' * @param {Dynamic} [msg=""] - message to display in the test report ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function fail(msg = "Error" as string) as dynamic + function fail(msg = "Error" as string, actual = "" as string, expected = "" as string) as dynamic if m.currentResult.isFail if m.throwOnFailedAssertion throw m.currentResult.getMessage() end if return false end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false + end function + + ' /** + ' * @memberof module:BaseTestSuite + ' * @name fail + ' * @function + ' * @instance + ' * @description Fail immediately, with the given message + ' * @param {Dynamic} [msg=""] - message to display in the test report + ' * @returns {boolean} - true if the assert was satisfied, false otherwise + ' */ + function skip(msg = "Skipped" as string) as dynamic + if m.currentResult.isFail + if m.throwOnFailedAssertion + throw m.currentResult.getMessage() + end if + return false + end if + m.currentResult.skip(msg) return false end function @@ -325,13 +330,16 @@ namespace rooibos ' * @param {Dynamic} [msg=""] - message to display in the test report ' * @returns {boolean} - true if failure was set, false if the test is already failed ' */ - function failCrash(error as dynamic, msg = "Error" as string) as dynamic + function failCrash(error as dynamic, msg = "" as string) as dynamic if m.currentResult.isFail if m.throwOnFailedAssertion throw m.currentResult.getMessage() end if return false end if + if msg = "" + msg = error.message + end if m.currentResult.fail(msg, m.currentAssertLineNumber) m.currentResult.crash(msg, error) return true @@ -354,24 +362,25 @@ namespace rooibos ' * @description Fail the test if the expression is true. ' * @param {Dynamic} expr - An expression to evaluate. ' * @param {Dynamic} [msg=""] - alternate error message - ' Default value: "Expression evaluates to true"' * @returns {boolean} - true if the assert was satisfied, false otherwise + ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertFalse(expr as dynamic, msg = "Expression evaluates to true") as dynamic + function assertFalse(expr as dynamic, msg = "") as dynamic if m.currentResult.isFail return false end if try - if rooibos.common.isBoolean(expr) - if expr - return m.fail(msg) + if not rooibos.common.isBoolean(expr) or expr + actual = rooibos.common.asMultilineString(expr, true) + expected = rooibos.common.asMultilineString(false, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(actual)}" to be ${rooibos.common.truncateString(expected)}` end if - else - return m.fail("value was not a boolean") + return m.fail(msg, actual, expected) end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false end function @@ -386,23 +395,24 @@ namespace rooibos ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertTrue(expr, msg = "Expression evaluates to false") + function assertTrue(expr, msg = "") if m.currentResult.isFail return false end if try - if rooibos.common.isBoolean(expr) - if not expr - return m.fail(msg) + if not rooibos.common.isBoolean(expr) or not expr + actual = rooibos.common.asMultilineString(expr, true) + expected = rooibos.common.asMultilineString(true, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(actual)}" to be ${rooibos.common.truncateString(expected)}` end if - else - return m.fail("value was not a boolean") + return m.fail(msg, actual, expected) end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false end function @@ -424,18 +434,20 @@ namespace rooibos end if try if not rooibos.common.eqValues(first, second) + actual = rooibos.common.asMultilineString(first, true) + expected = rooibos.common.asMultilineString(second, true) if msg = "" - first_as_string = rooibos.common.asString(first, true) - second_as_string = rooibos.common.asString(second, true) - msg = first_as_string + " != " + second_as_string + messageActual = rooibos.common.truncateString(actual) + messageExpected = rooibos.common.truncateString(expected) + msg = `expected "${messageActual}" to equal "${messageExpected}"` end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -458,18 +470,20 @@ namespace rooibos end if try if not rooibos.common.eqValues(first, second, true) + actual = rooibos.common.asMultilineString(first, true) + expected = rooibos.common.asMultilineString(second, true) if msg = "" - first_as_string = rooibos.common.asString(first) - second_as_string = rooibos.common.asString(second) - msg = first_as_string + " != " + second_as_string + messageActual = rooibos.common.truncateString(actual) + messageExpected = rooibos.common.truncateString(expected) + msg = `expected "${messageActual}" to be like "${messageExpected}"` end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -492,10 +506,12 @@ namespace rooibos end if try if rooibos.common.eqValues(first, second) + actual = rooibos.common.asMultilineString(first, true) + expected = rooibos.common.asMultilineString(second, true) if msg = "" - first_as_string = rooibos.common.asString(first, true) - second_as_string = rooibos.common.asString(second, true) - msg = first_as_string + " == " + second_as_string + messageActual = rooibos.common.truncateString(actual) + messageExpected = rooibos.common.truncateString(expected) + msg = `expected "${messageActual}" to not equal "${messageExpected}"` end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false @@ -503,7 +519,7 @@ namespace rooibos return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -525,10 +541,10 @@ namespace rooibos end if try - if value <> invalid + if rooibos.common.getSafeType(value) <> "Invalid" if msg = "" - expr_as_string = rooibos.common.asString(value, true) - msg = expr_as_string + " <> Invalid" + actual = rooibos.common.asMultilineString(value, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to be invalid` end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false @@ -536,7 +552,7 @@ namespace rooibos return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -557,9 +573,10 @@ namespace rooibos return false end if try - if value = invalid + if rooibos.common.getSafeType(value) = "Invalid" if msg = "" - msg = "Expected value, got invalid" + actual = rooibos.common.asMultilineString(value, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to not be invalid` end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false @@ -567,7 +584,7 @@ namespace rooibos return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -578,34 +595,37 @@ namespace rooibos ' * @name assertAAHasKey ' * @function ' * @instance - ' * @description Fail if the array doesn't have the key. - ' * @param {Dynamic} array - target array + ' * @description Fail if the aa doesn't have the key. + ' * @param {Dynamic} aa - target aa ' * @param {Dynamic} key - key name ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertAAHasKey(array, key, msg = "") as dynamic + function assertAAHasKey(aa, key, msg = "") as dynamic if m.currentResult.isFail return false end if try - if rooibos.common.isAssociativeArray(array) - if not array.DoesExist(key) - if msg = "" - msg = "Array doesn't have the '" + key + "' key." - end if - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(aa) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not aa.DoesExist(key) + if msg = "" + actual = rooibos.common.asMultilineString(aa, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to have property "${key}"` end if - else - msg = "Input value is not an Associative Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -616,35 +636,38 @@ namespace rooibos ' * @name assertAANotHasKey ' * @function ' * @instance - ' * @description Fail if the array has the key. - ' * @param {Dynamic} array - target array + ' * @description Fail if the aa has the key. + ' * @param {Dynamic} aa - target aa ' * @param {Dynamic} key - key name ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertAANotHasKey(array, key, msg = "") as dynamic + function assertAANotHasKey(aa, key, msg = "") as dynamic if m.currentResult.isFail return false end if try - if rooibos.common.isAssociativeArray(array) - if array.DoesExist(key) - if msg = "" - msg = "Array has the '" + key + "' key." - end if - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(aa) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if aa.DoesExist(key) + if msg = "" + actual = rooibos.common.asMultilineString(aa, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to not have property "${key}"` end if - else - msg = "Input value is not an Associative Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -655,40 +678,59 @@ namespace rooibos ' * @name assertAAHasKeys ' * @function ' * @instance - ' * @description Fail if the array doesn't have the keys list. - ' * @param {Dynamic} array - A target associative array. + ' * @description Fail if the aa doesn't have the keys list. + ' * @param {Dynamic} aa - A target associative array. ' * @param {Dynamic} keys - Array of key names. ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertAAHasKeys(array, keys, msg = "") as dynamic + function assertAAHasKeys(aa, keys, msg = "") as dynamic if m.currentResult.isFail return false end if try - if rooibos.common.isAssociativeArray(array) and rooibos.common.isArray(keys) - for each key in keys - if not array.DoesExist(key) - if msg = "" - msg = "Array doesn't have the '" + key + "' key." - end if - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end for - else - msg = "Input value is not an Associative Array." + if not rooibos.common.isAssociativeArray(aa) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isArray(keys) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(keys, true))}" to be an Array` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + foundKeys = [] + missingKeys = [] + for each key in keys + if not aa.DoesExist(key) + missingKeys.push(key) + else + foundKeys.push(key) + end if + end for + + if missingKeys.count() > 0 + actual = rooibos.common.asMultilineString(foundKeys, true) + expected = rooibos.common.asMultilineString(keys, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to have properties ${rooibos.common.truncateString(missingKeys.join(", "))}` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false + end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -696,36 +738,53 @@ namespace rooibos ' * @name assertAANotHasKeys ' * @function ' * @instance - ' * @description Fail if the array has the keys list. - ' * @param {Dynamic} array - A target associative array. + ' * @description Fail if the aa has the keys list. + ' * @param {Dynamic} aa - A target associative array. ' * @param {Dynamic} keys - Array of key names. ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ - function assertAANotHasKeys(array, keys, msg = "") as dynamic + function assertAANotHasKeys(aa, keys, msg = "") as dynamic if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) and rooibos.common.isArray(keys) - for each key in keys - if array.DoesExist(key) - if msg = "" - msg = "Array has the '" + key + "' key." - end if - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end for - else - msg = "Input value is not an Associative Array." + if not rooibos.common.isAssociativeArray(aa) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isArray(keys) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(keys, true))}" to be an Array` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + foundKeys = [] + for each key in keys + if aa.DoesExist(key) + foundKeys.push(formatJson(key)) + end if + end for + + if foundKeys.count() > 0 + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to not have properties ${rooibos.common.truncateString(foundKeys.join(", "))}` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -749,22 +808,27 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) or rooibos.common.isArray(array) - if not rooibos.common.arrayContains(array, value, key) - msg = "Array doesn't have the '" + rooibos.common.asString(value, true) + "' value." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.arrayContains(array, value, key) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` end if - else - msg = "Input value is not an Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -785,58 +849,66 @@ namespace rooibos if m.currentResult.isFail return false end if + try if not rooibos.common.isArray(values) - msg = "values to search for are not an Array." + if msg = "" + msg = `expected value "${rooibos.common.truncateString(rooibos.common.asMultilineString(values, true))}" must be an Array` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if - if rooibos.common.isArray(array) - for each value in values - isMatched = false - if not rooibos.common.isAssociativeArray(value) - msg = "Value to search for was not associativeArray " + rooibos.common.asString(value, true) - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isArray(array) + if msg = "" + msg = `actual value "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" must be an Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + for each value in values + isMatched = false + if not rooibos.common.isAssociativeArray(value) + if msg = "" + msg = `expected search value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an AssociativeArray` end if - for each item in array - if rooibos.common.IsAssociativeArray(item) - isValueMatched = true - for each key in value - fieldValue = value[key] - itemValue = item[key] - if not rooibos.common.eqValues(fieldValue, itemValue) - isValueMatched = false - exit for - end if - end for - if isValueMatched - isMatched = true + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + for each item in array + if rooibos.common.IsAssociativeArray(item) + isValueMatched = true + for each key in value + fieldValue = value[key] + itemValue = item[key] + if not rooibos.common.eqValues(fieldValue, itemValue) + isValueMatched = false exit for end if + end for + if isValueMatched + isMatched = true + exit for end if - end for ' items in array + end if + end for ' items in array - if not isMatched - msg = "array missing value: " + rooibos.common.asString(value, true) - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not isMatched + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if - end for 'values to match - else - msg = "Input value is not an Array." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if + end for 'values to match return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -856,25 +928,30 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) or rooibos.common.isArray(array) - if rooibos.common.arrayContains(array, value, key) - msg = "Array has the '" + rooibos.common.asString(value, true) + "' value." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.arrayContains(array, value, key) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` end if - else - msg = "Input value is not an Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -884,7 +961,7 @@ namespace rooibos ' * @instance ' * @description Fail if the array doesn't have the item subset. ' * @param {Dynamic} array - target array - ' * @param {Dynamic} subset - items to check presnece of + ' * @param {Dynamic} subset - items to check presence of ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ @@ -892,31 +969,64 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if (rooibos.common.isAssociativeArray(array) and rooibos.common.isAssociativeArray(subset)) or (rooibos.common.isArray(array) and rooibos.common.isArray(subset)) - isAA = rooibos.common.isAssociativeArray(subset) - for each item in subset - key = invalid - value = item - if isAA - key = item - value = subset[key] - end if - if not rooibos.common.arrayContains(array, value, key) - msg = "Array doesn't have the '" + rooibos.common.asString(value, true) + "' value." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end for - else - msg = "Input value is not an Array." + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isAssociativeArray(subset) and not rooibos.common.isArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isAssociativeArray(array) and not rooibos.common.isAssociativeArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isArray(array) and not rooibos.common.isArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an Array to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + isAA = rooibos.common.isAssociativeArray(subset) + for each item in subset + key = invalid + value = item + if isAA + key = item + value = subset[key] + end if + if not rooibos.common.arrayContains(array, value, key) + if msg = "" + if isAA + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain property "${rooibos.common.truncateString(key)}" with value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` + else + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` + end if + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + end for return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -929,7 +1039,7 @@ namespace rooibos ' * @instance ' * @description Fail if the array have the item from subset. ' * @param {Dynamic} array - target array - ' * @param {Dynamic} subset - items to check presnece of + ' * @param {Dynamic} subset - items to check presence of ' * @param {Dynamic} [msg=""] - alternate error message ' * @returns {boolean} - true if the assert was satisfied, false otherwise ' */ @@ -937,34 +1047,66 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if (rooibos.common.isAssociativeArray(array) and rooibos.common.isAssociativeArray(subset)) or (rooibos.common.isArray(array) and rooibos.common.isArray(subset)) - isAA = rooibos.common.isAssociativeArray(subset) - for each item in subset - key = invalid - value = item - if isAA - key = item - value = item[key] - end if - if rooibos.common.arrayContains(array, value, key) - msg = "Array has the '" + rooibos.common.asString(value, true) + "' value." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end for - else - msg = "Input value is not an Array." + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + if not rooibos.common.isAssociativeArray(subset) and not rooibos.common.isArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isAssociativeArray(array) and not rooibos.common.isAssociativeArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isArray(array) and not rooibos.common.isArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an Array to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + isAA = rooibos.common.isAssociativeArray(subset) + for each item in subset + key = invalid + value = item + if isAA + key = item + value = subset[key] + end if + if rooibos.common.arrayContains(array, value, key) + if msg = "" + if isAA + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain property "${rooibos.common.truncateString(key)}" with value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` + else + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"` + end if + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + end for return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -982,25 +1124,37 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) or rooibos.common.isArray(array) - if array.Count() <> count - msg = "Array items count " + rooibos.common.asString(array.Count()) + " <> " + rooibos.common.asString(count) + "." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isNumber(count) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(count, true))}" to be an Number` end if - else - msg = "Input value is not an Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + if array.count() <> count + if msg = "" + msg = `expected count "${array.Count()}" to be "${count}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, rooibos.common.asMultilineString(array.count(), true), rooibos.common.asMultilineString(count, true)) + return false + end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1018,25 +1172,38 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) or rooibos.common.isArray(array) - if array.Count() = count - msg = "Array items count = " + rooibos.common.asString(count) + "." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isNumber(count) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(count, true))}" to be an Number` end if - else - msg = "Input value is not an Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + if array.count() = count + if msg = "" + msg = `expected count "${array.count()}" to not be "${count}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1053,31 +1220,48 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(item) or rooibos.common.isArray(item) - if item.count() > 0 - msg = "Array is not empty." - m.currentResult.fail(msg, m.currentAssertLineNumber) + if rooibos.common.isAssociativeArray(item) + if not item.isEmpty() + actual = rooibos.common.asMultilineString(item, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(actual)}" to be empty` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, rooibos.common.asMultilineString({}, true)) + return false + end if + else if rooibos.common.isArray(item) + if not item.isEmpty() + actual = rooibos.common.asMultilineString(item, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(actual)}" to be empty` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, rooibos.common.asMultilineString([], true)) return false end if else if rooibos.common.isString(item) - if rooibos.common.asString(item) <> "" - msg = "String is not empty, contains: " + rooibos.common.asString(item, true) - m.currentResult.fail(msg, m.currentAssertLineNumber) + if not item.isEmpty() + actual = rooibos.common.asMultilineString(item, true) + if msg = "" + msg = `expected ${rooibos.common.truncateString(actual)} to be empty` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, "") return false end if else - msg = "AssertEmpty: Input value was not an array or a string" + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be an AssociativeArray, Array, or String` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1094,31 +1278,48 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(item) or rooibos.common.isArray(item) - if item.count() = 0 - msg = "Array is empty." + if rooibos.common.isAssociativeArray(item) + if item.isEmpty() + if msg = "" + actual = rooibos.common.asMultilineString(item, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to not be empty` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + else if rooibos.common.isArray(item) + if item.isEmpty() + if msg = "" + actual = rooibos.common.asMultilineString(item, true) + msg = `expected "${rooibos.common.truncateString(actual)}" to not be empty` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if else if rooibos.common.isString(item) - if item = "" - msg = "Input value is empty." + if item.isEmpty() + if msg = "" + actual = rooibos.common.asMultilineString(item, true) + msg = `expected ${rooibos.common.truncateString(actual)} to be empty` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if else - msg = "Input value is not a string or array." + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be an AssociativeArray, Array, or String` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1136,42 +1337,57 @@ namespace rooibos if m.currentResult.isFail return false end if - try + try if typeStr <> "String" and typeStr <> "Integer" and typeStr <> "Boolean" and typeStr <> "Array" and typeStr <> "AssociativeArray" - msg = "Type must be Boolean, String, Array, Integer, or AssociativeArray" + if msg = "" + msg = `expect type ${rooibos.common.asMultilineString(typeStr, true)} to be "Boolean", "String", "Integer", "Array", or "AssociativeArray"` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if - if rooibos.common.isAssociativeArray(array) or rooibos.common.isArray(array) - methodName = "Rooibos_Common_Is" + typeStr - typeCheckFunction = m.getIsTypeFunction(methodName) - if typeCheckFunction <> invalid - for each item in array - if not typeCheckFunction(item) - msg = rooibos.common.asString(item, true) + " is not a '" + typeStr + "' type." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end for - else - msg = "could not find comparator for type '" + typeStr + "' type." - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array` end if - else - msg = "Input value is not an Array." m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + + isAA = rooibos.common.isAssociativeArray(array) + methodName = "Rooibos_Common_Is" + typeStr + typeCheckFunction = m.getIsTypeFunction(methodName) + if typeCheckFunction <> invalid + for each item in array + key = invalid + if isAA + key = item + item = array[key] + end if + if not typeCheckFunction(item) + if msg = "" + if isAA + msg = `expected "${rooibos.common.truncateString(key)}: ${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be type ${rooibos.common.asMultilineString(typeStr, true)}` + else + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be type ${rooibos.common.asMultilineString(typeStr, true)}` + end if + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + end for + else + ' I think we can remove this check, as we are already checking for valid types? + ' Will revisit this later. + throw `could not find comparator for type ${rooibos.common.asMultilineString(typeStr, true)}` + end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function function getIsTypeFunction(name) @@ -1225,22 +1441,23 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(value) <> typeStr + actual = rooibos.common.asMultilineString(type(value), true) + expected = rooibos.common.asMultilineString(typeStr, true) if msg = "" - expr_as_string = rooibos.common.asString(value, true) - msg = expr_as_string + " was not expected type " + typeStr + msg = `expected ${rooibos.common.truncateString(actual)} to be type ${rooibos.common.truncateString(expected)}` end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1258,51 +1475,72 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(value) <> "roSGNode" + actual = rooibos.common.getTypeWithComponentWrapper(value) + expected = `` + if msg = "" - expr_as_string = rooibos.common.asString(value, true) - msg = expr_as_string + " was not a node, so could not match subtype " + typeStr + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false else if value.subType() <> typeStr + actual = rooibos.common.getTypeWithComponentWrapper(value, true) + expected = `` if msg = "" - expr_as_string = rooibos.common.asString(value, true) - msg = expr_as_string + "( type : " + value.subType() + ") was not of subType " + typeStr + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` end if - m.currentResult.fail(msg, m.currentAssertLineNumber) + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function - function assertClass(value, func, msg = "") as dynamic + function assertClass(value, expectedClassName, msg = "") as dynamic if m.currentResult.isFail return false end if - if rooibos.common.isFunction(func) - func = func.toStr().mid(10).replace("_", ".") + if rooibos.common.isFunction(expectedClassName) + expectedClassName = expectedClassName.toStr().mid(10).replace("_", ".") end if + try if not rooibos.common.isAssociativeArray(value) if msg = "" - expr_as_string = rooibos.common.asString(value) - msg = expr_as_string + " was not an aa, so could not match Class " + func + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an instance of ${rooibos.common.truncateString(rooibos.common.asMultilineString(expectedClassName, true))}` end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if - className = lCase(`${value.__classname}`) - if className <> lCase(func) + + if not rooibos.common.isString(value?.__classname) if msg = "" - msg = `Expected class: ${func} got ${className}` + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an instance of ${rooibos.common.truncateString(rooibos.common.asMultilineString(expectedClassName, true))}` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + className = value?.__classname + fail = false + if not rooibos.common.isString(value?.__classname) + className = "Invalid" + fail = true + end if + className = lCase(className) + + if fail or className <> lCase(expectedClassName) + actual = rooibos.common.asMultilineString(className, true) + expected = rooibos.common.asMultilineString(lCase(expectedClassName), true) + if msg = "" + msg = `expected class ${rooibos.common.truncateString(actual)} to be an instance of ${rooibos.common.truncateString(expected)}` end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false @@ -1310,10 +1548,9 @@ namespace rooibos return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function @@ -1326,7 +1563,7 @@ namespace rooibos ' * @name assertNodeCount ' * @function ' * @instance - ' * @description Asserts that the node contains the desginated number of children + ' * @description Asserts that the node contains the designated number of children ' * @param {Dynamic} node - target node ' * @param {Dynamic} count - expected number of child items ' * @param {Dynamic} [msg=""] - alternate error message @@ -1336,32 +1573,38 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(node) = "roSGNode" if node.isSubType("mc_Node") - if node.isSubType("mc_Node") - childCount = node.length - else - childCount = node.getChildCount() - end if - if childCount <> count - msg = "node items count <> " + rooibos.common.asString(count) + ". Received " + rooibos.common.asString(childCount) - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false + childCount = node.length + else + childCount = node.getChildCount() + end if + if childCount <> count + actual = rooibos.common.asMultilineString(childCount, true) + expected = rooibos.common.asMultilineString(count, true) + if msg = "" + msg = `expected count "${actual}" to be "${expected}"` end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false end if else - msg = "Input value is not an node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1379,6 +1622,7 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(node) = "roSGNode" if node.isSubType("mc_Node") @@ -1387,22 +1631,29 @@ namespace rooibos childCount = node.getChildCount() end if if childCount = count - msg = "node items count = " + rooibos.common.asString(count) + "." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.asMultilineString(childCount, true) + expected = rooibos.common.asMultilineString(count, true) + if msg = "" + msg = `expected count "${actual}" to not be "${expected}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if else - msg = "Input value is not an node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1419,6 +1670,7 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(node) = "roSGNode" if node.isSubType("mc_Node") @@ -1427,18 +1679,27 @@ namespace rooibos childCount = node.getChildCount() end if if childCount > 0 - msg = "node is not empty." + if msg = "" + msg = `expected child count "${childCount}" to be "0"` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + else + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1455,21 +1716,37 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(node) = "roSGNode" - if node.Count() = 0 - msg = "Array is empty." + if node.isSubType("mc_Node") + childCount = node.length + else + childCount = node.getChildCount() + end if + + if childCount = 0 + if msg = "" + msg = `expected child count "${childCount}" to be greater then "0"` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if + else + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1487,25 +1764,31 @@ namespace rooibos if m.currentResult.isFail return false end if + try if type(node) = "roSGNode" if not rooibos.common.nodeContains(node, value) - msg = "Node doesn't have the '" + rooibos.common.asString(value, true) + "' value." + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if else - msg = "Input value is not an Node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function ' /** @@ -1526,7 +1809,9 @@ namespace rooibos try if type(node) = "roSGNode" if not rooibos.common.nodeContains(node, value) - msg = "Node doesn't have the '" + rooibos.common.asString(value, true) + "' value." + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false else @@ -1535,21 +1820,28 @@ namespace rooibos else childCount = node.getChildCount() end if + if childCount <> 1 - msg = "Node Contains specified value; but other values as well" + if msg = "" + msg = `expected child count "${childCount}" to be "1"` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if end if else - msg = "Input value is not an Node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -1574,19 +1866,25 @@ namespace rooibos try if type(node) = "roSGNode" if rooibos.common.nodeContains(node, value) - msg = "Node has the '" + rooibos.common.asString(value, true) + "' value." + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to not contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if else - msg = "Input value is not an Node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false @@ -1607,36 +1905,67 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if (type(node) = "roSGNode" and rooibos.common.isAssociativeArray(subset)) or (type(node) = "roSGNode" and rooibos.common.isArray(subset)) - isIgnoredFields = rooibos.common.isArray(ignoredFields) + if not type(node) = "roSGNode" + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false + end if + + if not rooibos.common.isAssociativeArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isArray(ignoredFields) + filteredSubset = {} for each key in subset - if key <> "" - if not isIgnoredFields or not rooibos.common.arrayContains(ignoredFields, key) - subsetValue = subset[key] - nodeValue = node[key] - if not rooibos.common.eqValues(nodeValue, subsetValue) - msg = key + ": Expected '" + rooibos.common.asString(subsetValue, true) + "', got '" + rooibos.common.asString(nodeValue, true) + "'" - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end if - else - ? "Found empty key!" + if rooibos.common.isString(key) and not rooibos.common.arrayContains(ignoredFields, key) + filteredSubset[key] = subset[key] end if end for else - msg = "Input value is not an Node." - m.currentResult.fail(msg, m.currentAssertLineNumber) + filteredSubset = subset + end if + + foundValues = {} + missingValues = {} + for each key in filteredSubset + subsetValue = filteredSubset[key] + nodeValue = node[key] + if rooibos.common.eqValues(nodeValue, subsetValue) + foundValues[key] = subsetValue + else + missingValues[key] = subsetValue + end if + end for + + if foundValues.count() <> filteredSubset.count() + actual = rooibos.common.asMultilineString(foundValues, true) + expected = rooibos.common.asMultilineString(filteredSubset, true) + if msg = "" + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(missingValues))}"` + end if + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, error.message) + return false end try return false - end function ' /** @@ -1654,34 +1983,63 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if (type(node) = "roSGNode" and rooibos.common.isAssociativeArray(subset)) or (type(node) = "roSGNode" and rooibos.common.isArray(subset)) - isAA = rooibos.common.isAssociativeArray(subset) - for each item in subset - key = invalid - value = item - if isAA - key = item - value = item[key] - end if + if not type(node) = "roSGNode" + actual = rooibos.common.getTypeWithComponentWrapper(node) + expected = `` + if msg = "" + msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false + end if + + if rooibos.common.isArray(subset) + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + ' NOTE: Legacy check for children via array support. + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + for each value in subset if rooibos.common.nodeContains(node, value) - msg = "Node has the '" + rooibos.common.asString(value, true) + "' value." + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to not contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if end for + else if rooibos.common.isAssociativeArray(subset) + foundValues = {} + for each key in subset + subsetValue = subset[key] + nodeValue = node[key] + if rooibos.common.eqValues(nodeValue, subsetValue) + foundValues[key] = subsetValue + end if + end for + + if foundValues.count() > 0 + actual = rooibos.common.asMultilineString(foundValues, true) + expected = rooibos.common.asMultilineString({}, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to have not have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(foundValues))}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) + return false + end if else - msg = "Input value is not an Node." + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray` + end if m.currentResult.fail(msg, m.currentAssertLineNumber) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -1704,36 +2062,62 @@ namespace rooibos if m.currentResult.isFail return false end if + try - if rooibos.common.isAssociativeArray(array) and rooibos.common.isAssociativeArray(subset) - isIgnoredFields = rooibos.common.isArray(ignoredFields) + if not rooibos.common.isAssociativeArray(array) + if msg = "" + msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if not rooibos.common.isAssociativeArray(subset) + if msg = "" + msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber) + return false + end if + + if rooibos.common.isArray(ignoredFields) + filteredSubset = {} for each key in subset - if key <> "" - if not isIgnoredFields or not rooibos.common.arrayContains(ignoredFields, key) - subsetValue = subset[key] - arrayValue = array[key] - if not rooibos.common.eqValues(arrayValue, subsetValue) - msg = key + ": Expected '" + rooibos.common.asString(subsetValue, true) + "', got '" + rooibos.common.asString(arrayValue, true) + "'" - m.currentResult.fail(msg, m.currentAssertLineNumber) - return false - end if - end if - else - ? "Found empty key!" + if rooibos.common.isString(key) and not rooibos.common.arrayContains(ignoredFields, key) + filteredSubset[key] = subset[key] end if end for else - msg = "Input values are not an Associative Array." - m.currentResult.fail("Assert failed: " + msg, m.currentAssertLineNumber) + filteredSubset = subset + end if + + foundValues = {} + missingValues = {} + for each key in filteredSubset + subsetValue = filteredSubset[key] + nodeValue = array[key] + if rooibos.common.eqValues(nodeValue, subsetValue) + foundValues[key] = subsetValue + else + missingValues[key] = subsetValue + end if + end for + + if foundValues.count() <> filteredSubset.count() + actual = rooibos.common.asMultilineString(foundValues, true) + expected = rooibos.common.asMultilineString(filteredSubset, true) + if msg = "" + msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(missingValues))}"` + end if + m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected) return false end if return true catch error 'bs:disable-next-line - m.currentResult.fail("Assert failed: " + error.message, m.currentAssertLineNumber) + m.currentResult.failCrash(error, msg) end try return false - end function @@ -1769,8 +2153,8 @@ namespace rooibos m.__stubId++ if m.__stubId > 25 - ? "ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # " ; m.__mockId - ? " Method was " ; methodName + rooibos.common.logError(`ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # ${m.__mockId}`) + rooibos.common.logError("Method was " + methodName) return invalid end if @@ -1789,10 +2173,10 @@ namespace rooibos ' FIXME: add a log setting for this - and add better detection so that stubs know that they are colliding/don't exist/have correct sigs ' if not isMethodPresent - ' ? "WARNING - stubbing call " ; methodName; " which did not exist on target object" + ' rooibos.common.logWarning(`stubbing call ${methodName} which did not exist on target object`) ' end if else - ? "ERROR - could not create Stub : method not found "; target ; "." ; methodName + rooibos.common.logTrace("Could not create Stub : method not found " + rooibos.common.asString(target) + "." + methodName) end if end if @@ -2019,7 +2403,7 @@ namespace rooibos end if if m.currentResult.isFail - ? "ERROR! Cannot create MOCK. method " ; methodName ;" " str(lineNumber) ; " "; m.currentResult.message + rooibos.common.logError(`Cannot create MOCK. method ${methodName} ${lineNumber} ${m.currentResult.message}`) return {} end if @@ -2054,8 +2438,8 @@ namespace rooibos m.__mockId++ id = stri(m.__mockId).trim() if m.__mockId > 25 - ? "ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # " ; m.__mockId - ? " Method was " ; methodName + rooibos.common.logError(`ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # ${m.__mockId}`) + rooibos.common.logError("Method was " + methodName) return invalid end if @@ -2071,10 +2455,10 @@ namespace rooibos target.__mocks = m.mocks if not isMethodPresent - ' ? "WARNING - mocking call " ; methodName; " which did not exist on target object" + rooibos.common.logWarning(`mocking call ${methodName} which did not exist on target object`) end if else - ? "ERROR - could not create Mock : method not found "; target ; "." ; methodName + rooibos.common.logError(`Could not create Mock : method not found ${target}.${methodName}`) end if end if else @@ -2117,7 +2501,7 @@ namespace rooibos if not rooibos.common.isUndefined(value) if rooibos.common.isAssociativeArray(value) and rooibos.common.isValid(value.matcher) if not rooibos.common.isFunction(value.matcher) - ? "[ERROR] you have specified a matching function; but it is not in scope!" + rooibos.common.logError("You have specified a matching function; but it is not in scope!") expectedArgsValues.push("#ERR-OUT_OF_SCOPE_MATCHER!") else expectedArgsValues.push(expectedArgs[i]) @@ -2146,7 +2530,7 @@ namespace rooibos expectedArgs: expectedArgsValues, expectedInvocations: expectedInvocations, callback: function(arg1 = invalid, arg2 = invalid, arg3 = invalid, arg4 = invalid, arg5 = invalid, arg6 = invalid, arg7 = invalid, arg8 = invalid, arg9 = invalid, arg10 = invalid, arg11 = invalid, arg12 = invalid, arg13 = invalid, arg14 = invalid, arg15 = invalid) as dynamic - ' ? "FAKE CALLBACK CALLED FOR " ; m.methodName + rooibos.common.logTrace(`FAKE CALLBACK CALLED FOR ${m.methodName}`) 'bs:disable-next-line if m.allInvokedArgs = invalid 'bs:disable-next-line @@ -2176,11 +2560,11 @@ namespace rooibos 'bs:disable-next-line if returnValues.count() <= m.invocations returnIndex = returnValues.count() - 1 - print "Multi return values all used up - repeating last value" + rooibos.common.logDebug("Multi return values all used up - repeating last value") end if return returnValues[returnIndex] else - ? "Multi return value was specified; but no array of results were found" + rooibos.common.logError("Multi return value was specified; but no array of results were found") return invalid end if else @@ -2684,7 +3068,7 @@ namespace rooibos if attempts = maxAttempts return false end if - ? "waiting for signal field '" ; fieldName "' - " ; attempts + rooibos.common.logDebug(`Waiting for signal field '${fieldName}' - ${attempts}`) end while return true @@ -2696,27 +3080,28 @@ namespace rooibos end function function setAsync(isAsync as boolean) - ? "Setting current test to async " isAsync + rooibos.common.logTrace(`Setting current test to async ${isAsync}`) m.top.rooibosTestIsAsync = isAsync end function function done() - ' ? "Async test is complete" + rooibos.common.logTrace("Async test is complete") if m.isDoneCalled = false m.isDoneCalled = true m.top.rooibosTestFinished = true else - ? "WARNING - extra done call after test is done ! Did you properly clean up your observers?" + rooibos.common.logWarning("extra done call after test is done! Did you properly clean up your observers?") end if end function function testSuiteDone() - ' ? "Async suite is complete" + rooibos.common.logTrace("Async suite is complete") + m.notifyReportersOnSuiteComplete() m.top.rooibosSuiteFinished = true end function function testGroupDone() - ? "Async group is complete" + rooibos.common.logTrace("Async group is complete") m.top.rooibosGroupFinished = true end function @@ -2765,16 +3150,32 @@ namespace rooibos end for end function + private sub notifyReportersOnSuiteBegin() + for each reporter in m.testReporters + if rooibos.common.isFunction(reporter.onSuiteBegin) + reporter.onSuiteBegin({ suite: m }) + end if + end for + end sub + + private sub notifyReportersOnSuiteComplete() + for each reporter in m.testReporters + if rooibos.common.isFunction(reporter.onSuiteComplete) + reporter.onSuiteComplete({ suite: m }) + end if + end for + end sub + end class function onGroupComplete() - ' ? "++++++++ THE GROUP COMPLETED" + rooibos.common.logTrace("++++++++ THE GROUP COMPLETED") m.testRunner.currentTestSuite.onAsyncGroupComplete() end function function onTestTimer() - ' ? "++++++++ TEST TIMED OUT" + rooibos.common.logTrace("++++++++ TEST TIMED OUT") m.testRunner.currentTestSuite.failBecauseOfTimeOut() end function diff --git a/framework/src/source/rooibos/CommonUtils.bs b/framework/src/source/rooibos/CommonUtils.bs index 38451201..5c444028 100755 --- a/framework/src/source/rooibos/CommonUtils.bs +++ b/framework/src/source/rooibos/CommonUtils.bs @@ -1,3 +1,9 @@ +#const ROOIBOS_ERROR_LOGS = true +#const ROOIBOS_WARNING_LOGS = false +#const ROOIBOS_INFO_LOGS = true +#const ROOIBOS_DEBUG_LOGS = false +#const ROOIBOS_TRACE_LOGS = false + namespace rooibos.common ' /** ' * @module CommonUtils @@ -295,6 +301,79 @@ namespace rooibos.common end if end function + ' /** + ' * @name asMultilineString + ' * @function + ' * @description convert input to multiline String if this possible, else return empty string + ' * @memberof module:CommonUtils + ' * @param {Dynamic} input - value to check + ' * @returns {String} - converted string + ' */ + function asMultilineString(input, includeType = false, indention = 0) as string + indentChr = " " + + if rooibos.common.isValid(input) = false + return type(input) + else if rooibos.common.isString(input) + return formatJson(input) + else if rooibos.common.isInteger(input) or rooibos.common.isLongInteger(input) or rooibos.common.isBoolean(input) + if includeType + return input.ToStr() + " (" + rooibos.common.getSafeType(input) + ")" + else + return input.ToStr() + end if + else if rooibos.common.isFloat(input) or rooibos.common.isDouble(input) + if includeType + return Str(input).Trim() + " (" + rooibos.common.getSafeType(input) + ")" + else + return Str(input).Trim() + end if + else if type(input) = "roSGNode" + return "Node(" + input.subType() + ")" + else if type(input) = "roAssociativeArray" + if input.isEmpty() + return "{" + chr(10) + "}" + end if + + text = "{" + chr(10) + keys = input.keys() + keys.sort() + for each key in keys + if rooibos.common.canSafelyIterateAAKey(input, key) + text = text + string(indention + 1, indentChr) + formatJson(key) + ": " + rooibos.common.asMultilineString(input[key], includeType, indention + 1) + "," + chr(10) + end if + end for + + ' remove last comma + if len(text) > 2 + text = left(text, len(text) - 2) + end if + + text = text + chr(10) + string(indention, indentChr) + "}" + return text + else if rooibos.common.isArray(input) + if input.isEmpty() + return "[" + chr(10) + "]" + end if + text = "[" + chr(10) + for i = 0 to input.count() - 1 + v = input[i] + text += string(indention + 1, indentChr) + rooibos.common.asMultilineString(v, includeType, indention + 1) + + if i < input.count() - 1 + text += "," + end if + text += chr(10) + end for + text = text + string(indention, indentChr) + "]" + return text + else if rooibos.common.isFunction(input) + return input.toStr().mid(10) + " (function)" + else + return "" + end if + end function + ' /** ' * @name AsString ' * @function @@ -610,8 +689,10 @@ namespace rooibos.common function getSafeType(v) t = type(v) - if t = "" or t = "" + if t = "" return invalid + else if t = "" + return "" else if t = "roString" return "String" else if t = "roInteger" @@ -634,27 +715,73 @@ namespace rooibos.common return t end if end function + + ' /** + ' * @memberof module:CommonUtils + ' * @name getTypeWithComponentWrapper + ' * @function + ' * @instance + ' * @description Takes a value and if the value is not a primitive it will wrap the type in a Component: tag like the debugger does + ' * @param {Dynamic} value - value to check the type of + ' * @param {Boolean} includeSubtype - If true and the value is a node the result will include the node subtype + ' * @returns {string} - Formatted result. Examples: 'String', 'Integer', '', '' + ' */ + function getTypeWithComponentWrapper(value, includeSubtype = false) as string + if not rooibos.common.isValid(value) or rooibos.common.isNumber(value) or rooibos.common.isString(value) or rooibos.common.isBoolean(value) + return type(value) + else + if includeSubtype and rooibos.common.isSGNode(value) + return `` + else + return `` + end if + end if + end function + + ' /** + ' * @memberof module:CommonUtils + ' * @name truncateString + ' * @function + ' * @instance + ' * @description Takes a string and formats and truncates a string for more compact printing. + ' * @param {Dynamic} value - string to format + ' * @param {Integer} maxLength - the max length of the resulting string + ' * @param {Boolean} collapseNewlines - Will convert newlines and spaces into a single space + ' * @returns {String} - Formatted result + ' */ + function truncateString(value as string, length = 38 as integer, collapseNewlines = true as boolean) as string + if collapseNewlines + value = CreateObject("roRegex", "\n\s*", "g").replaceAll(value, " ") + end if + + if len(value) > length + value = value.mid(0, length - 1) + "…" + end if + return value + end function + + ' /** ' * @memberof module:CommonUtils ' * @name EqValues ' * @function ' * @instance - ' * @description Compare two arbtrary values to eachother. + ' * @description Compare two arbitrary values to each-other. ' * @param {Dynamic} Value1 - first item to compare ' * @param {Dynamic} Value2 - second item to compare ' * @returns {boolean} - True if values are equal or False in other case. ' */ function eqValues(Value1, Value2, fuzzy = false, callCount = 0) as dynamic if callCount > 10 - ? "REACHED MAX ITERATIONS DOING COMPARISON" + rooibos.common.logError("REACHED MAX ITERATIONS DOING COMPARISON") return true end if - ' Workaraund for bug with string boxing, and box everything else + ' Workaround for bug with string boxing, and box everything else val1Type = rooibos.common.getSafeType(Value1) val2Type = rooibos.common.getSafeType(Value2) if val1Type = invalid or val2Type = invalid - ? "ERROR!!!! - undefined value passed" + rooibos.common.logError("undefined value passed") return false end if @@ -682,6 +809,12 @@ namespace rooibos.common else return Value1.isSameNode(Value2) end if + else if valtype = "" and val2Type = "" + ' Both values are uninitialized, so they are equal + return true + else if valtype = "" or val2Type = "" + ' One value is uninitialized, so they are not equal due to passing previous check + return false else if fuzzy = true return rooibos.common.asString(Value1) = rooibos.common.asString(Value2) @@ -699,7 +832,8 @@ namespace rooibos.common val1Type = rooibos.common.getSafeType(Value1) val2Type = rooibos.common.getSafeType(Value2) if val1Type = invalid or val2Type = invalid - ? "ERROR!!!! - undefined value passed" + ' TODO: this doesn't actually feel like an error, Need to talk about this. + rooibos.common.logError("undefined value passed") return false end if @@ -725,6 +859,9 @@ namespace rooibos.common ' * @returns {boolean} - True if arrays are equal or False in other case. ' */ function eqAssocArray(Value1, Value2, fuzzy = false, callCount = 0) as dynamic + if not rooibos.common.isAssociativeArray(Value1) or not rooibos.common.isAssociativeArray(Value2) + return false + end if l1 = Value1.Count() l2 = Value2.Count() @@ -770,7 +907,7 @@ namespace rooibos.common ' */ function eqArray(Value1, Value2, fuzzy = false, callCount = 0) as dynamic if callCount > 30 - ? "REACHED MAX ITERATIONS DOING COMPARISON" + rooibos.common.logError("REACHED MAX ITERATIONS DOING COMPARISON") return true end if if not (rooibos.common.isArray(Value1)) or not rooibos.common.isArray(Value2) @@ -863,5 +1000,34 @@ namespace rooibos.common return part end function + sub logError(value) + #if ROOIBOS_ERROR_LOGS + ? "[Rooibos Error]: " value + #end if + end sub + + sub logWarning(value) + #if ROOIBOS_WARNING_LOGS + ? "[Rooibos Warning]: " value + #end if + end sub + + sub logInfo(value) + #if ROOIBOS_INFO_LOGS + ? "[Rooibos Info]: " value + #end if + end sub + + sub logDebug(value) + #if ROOIBOS_DEBUG_LOGS + ? "[Rooibos Debug]: " value + #end if + end sub + + sub logTrace(value) + #if ROOIBOS_TRACE_LOGS + ? "[Rooibos Trace]: " value + #end if + end sub end namespace \ No newline at end of file diff --git a/framework/src/source/rooibos/ConsoleTestReporter.bs b/framework/src/source/rooibos/ConsoleTestReporter.bs index d33abb0c..bbbadc93 100644 --- a/framework/src/source/rooibos/ConsoleTestReporter.bs +++ b/framework/src/source/rooibos/ConsoleTestReporter.bs @@ -13,16 +13,54 @@ namespace rooibos end if end function - override function onEnd(ev as rooibos.ITestReporterOnEndEvent) - m.allStats = ev.stats + override function onSuiteBegin(event as rooibos.TestReporterOnSuiteBeginEvent) + if event.suite.isIgnored then return invalid + ? "" + ? rooibos.common.fillText("> SUITE: " + event.suite.name, ">", 80) + end function + + override function onTestGroupBegin(event as rooibos.TestReporterOnTestGroupBeginEvent) + if event.group.isIgnored then return invalid + ? "" + ? rooibos.common.fillText(">>>> Describe: " + event.group.name, ">", 80) + 'bs:disable-next-line + ? ` Location: file://${event.group.testSuite.filePath.trim()}:${event.group.lineNumber}` + ? "" + end function + + override function onTestBegin(event as rooibos.TestReporterOnTestBeginEvent) + if event.test.isIgnored then return invalid + ? "" + ? rooibos.common.fillText(">>>>>> It: " + event.test.name, ">", 80) + ? ` Location: file://${event.test.testSuite.filePath.trim()}:${event.test.lineNumber}` + ? "" + end function + + override function onTestComplete(event as rooibos.TestReporterOnTestCompleteEvent) + if event.test.isIgnored then return invalid + ? rooibos.common.fillText("<<<< END It: " + event.test.name + " (" + event.test.result.getStatusText() + ") ", "<", 80) + end function + + ' override function onTestGroupComplete(event as rooibos.TestReporterOnTestGroupCompleteEvent) + ' 'override me + ' end function + + ' override function onSuiteComplete(event as rooibos.TestReporterOnSuiteCompleteEvent) + ' 'override me + ' end function + + override function onEnd(event as rooibos.TestReporterOnEndEvent) + m.allStats = event.stats m.startReport() for each testSuite in m.testRunner.testSuites - if not m.allStats.hasFailures or ((not m.config.showOnlyFailures) or testSuite.stats.failedCount > 0 or testSuite.stats.crashedCount > 0) - m.printSuiteStart(testSuite) + if not testSuite.isIgnored + if not m.allStats.hasFailures or ((not m.config.showOnlyFailures) or testSuite.stats.failedCount > 0 or testSuite.stats.crashedCount > 0) + m.printSuiteStart(testSuite) - for each testGroup in testSuite.groups - m.printGroup(testGroup) - end for + for each testGroup in testSuite.groups + m.printGroup(testGroup) + end for + end if end if end for @@ -59,16 +97,19 @@ namespace rooibos end function function printGroup(testGroup) + if testGroup.isIgnored then return invalid isGroupPrinted = false for each test in testGroup.tests - if not m.allStats.hasFailures or ((not m.config.showOnlyFailures) or test.result.isFail) - if not isGroupPrinted - m.printGroupStart(testGroup) - isGroupPrinted = true - end if + if not test.result.isSkipped + if not m.allStats.hasFailures or ((not m.config.showOnlyFailures) or test.result.isFail) + if not isGroupPrinted + m.printGroupStart(testGroup) + isGroupPrinted = true + end if - m.printTest(test) + m.printTest(test) + end if end if end for @@ -88,7 +129,7 @@ namespace rooibos locationLine = StrI(test.lineNumber).trim() else if test.result.isFail testChar = "-" - locationLine = StrI(test.result.lineNumber + 1).trim() + locationLine = StrI(test.result.lineNumber).trim() else testChar = "|" locationLine = StrI(test.lineNumber).trim() @@ -141,7 +182,7 @@ namespace rooibos else if test.result.isFail m.printLine(0, " | " + insetText + " |--Failed at: " + locationText) if test.isParamTest = true - m.printLine(0, " | " + insetText + " |--Param Line: " + StrI(test.paramLineNumber - 1).trim()) + m.printLine(0, " | " + insetText + " |--Param Line: " + StrI(test.paramLineNumber).trim()) end if m.printLine(0, " | " + insetText + " |--Error Message: " + test.result.message) m.printLine(0, " | ") diff --git a/framework/src/source/rooibos/JUnitTestReporter.bs b/framework/src/source/rooibos/JUnitTestReporter.bs index 4fe3346a..a963fc36 100644 --- a/framework/src/source/rooibos/JUnitTestReporter.bs +++ b/framework/src/source/rooibos/JUnitTestReporter.bs @@ -1,7 +1,7 @@ namespace rooibos class JUnitTestReporter extends rooibos.BaseTestReporter - override function onEnd(ev as rooibos.ITestReporterOnEndEvent) + override function onEnd(event as rooibos.TestReporterOnEndEvent) root = createObject("roXMLElement") root.SetName("testsuites") properties = root.addElement("properties") diff --git a/framework/src/source/rooibos/MochaTestReporter.bs b/framework/src/source/rooibos/MochaTestReporter.bs new file mode 100644 index 00000000..f9137b0a --- /dev/null +++ b/framework/src/source/rooibos/MochaTestReporter.bs @@ -0,0 +1,967 @@ +namespace rooibos + class MochaTestReporter extends rooibos.BaseTestReporter + + sub new(runner) + 'bs:disable-next-line + super(runner) + end sub + + private failureCount = 0 + + ' override function onBegin(event as rooibos.TestReporterOnBeginEvent) + ' 'override me + ' end function + + override sub onSuiteBegin(event as rooibos.TestReporterOnSuiteBeginEvent) + print m.colorLines(rooibos.reporters.mocha.colors.suite, event.suite.name) + end sub + + override sub onTestGroupBegin(event as rooibos.TestReporterOnTestGroupBeginEvent) + print tab(2) m.colorLines(rooibos.reporters.mocha.colors.suite, event.group.name) + end sub + + ' override function onTestBegin(event as rooibos.TestReporterOnTestBeginEvent) + ' 'override me + ' end function + + override sub onTestComplete(event as rooibos.TestReporterOnTestCompleteEvent) + test = event.test + status = test.result.getStatusText() + + lineColor = rooibos.reporters.mocha.colors.light + symbolColor = "" + symbol = "?" + if status = "PASS" + symbol = "✔" + symbolColor = rooibos.reporters.mocha.colors.checkmark + else if status = "FAIL" or status = "CRASH" + symbol = "✖" + symbolColor = rooibos.reporters.mocha.colors.brightFail + else if status = "SKIP" + symbol = "-" + symbolColor = rooibos.reporters.mocha.colors.pending + lineColor = rooibos.reporters.mocha.colors.pending + end if + + params = "" + if test.isParamTest + rawParams = invalid + if type(test.rawParams) = "roAssociativeArray" + rawParams = {} + for each key in test.rawParams + if type(test.rawParams[key]) <> "Function" and type(test.rawParams[key]) <> "roFunction" + rawParams[key] = test.rawParams[key] + end if + end for + else + rawParams = test.rawParams + end if + + params = " " + formatJson(rawParams) + end if + + duration = "" + if test.result.time > test.slow + duration = m.colorLines(rooibos.reporters.mocha.colors.slow, ` (${test.result.time}ms)`) + else if test.result.time > test.slow / 2 + duration = m.colorLines(rooibos.reporters.mocha.colors.medium, ` (${test.result.time}ms)`) + ' else if test.result.time > slow / 4 + ' duration = m.colorLines(rooibos.reporters.mocha.colors.fast, ` (${test.result.time}ms)`) + end if + + print tab(4) m.colorLines(symbolColor, symbol) + " " + m.colorLines(lineColor, test.name + params) + duration + end sub + + ' override function onTestGroupComplete(event as rooibos.TestReporterOnTestGroupCompleteEvent) + ' 'override me + ' end function + + ' override function onSuiteComplete(event as rooibos.TestReporterOnSuiteCompleteEvent) + ' 'override me + ' end function + + override sub onEnd(event as rooibos.TestReporterOnEndEvent) + print m.formatStatsString(event.stats) + + for each testSuite in m.testRunner.testSuites + for each testGroup in testSuite.groups + m.logFailures(testGroup) + end for + end for + end sub + + ' Creates a formatted string from the stats object + ' example: + '` + ' 327 passed (5113ms) + ' 1 crashed + ' 207 failing + ' 8 skipped + '` + function formatStatsString(stats as rooibos.Stats) as string + statusString = chr(10) + + indent = string(1, chr(9)) + statusString += `${indent}${m.colorLines(rooibos.reporters.mocha.colors.brightPass, `${stats.passedCount} passed`)} ${m.colorLines(rooibos.reporters.mocha.colors.light, ` (${stats.time}ms)`)}` + + if stats.crashedCount > 0 + statusString += chr(10) + m.colorLines(rooibos.reporters.mocha.colors.fail, `${indent}${stats.crashedCount} crashed`) + end if + + if stats.failedCount > 0 + statusString += chr(10) + m.colorLines(rooibos.reporters.mocha.colors.fail, `${indent}${stats.failedCount} failing`) + end if + + if stats.ignoredCount > 0 + statusString += chr(10) + m.colorLines(rooibos.reporters.mocha.colors.pending, `${indent}${stats.ignoredCount} skipped`) + end if + + statusString += chr(10) + return statusString + end function + + + ' Logs all failures for a given test group + ' example: + + ' 1) Rooibos failed assertion tests + ' tests fail on crash + ' reports error: + ' + ' Error: some error + ' $anon_6c() As Dynamic (pkg:/source/FailedAssertion.spec.brs:11) + ' $anon_303() As Dynamic (pkg:/source/rooibos/Test.brs:45) + ' $anon_1f2(test As Object) As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:243) + ' $anon_30a() As Dynamic (pkg:/source/rooibos/TestGroup.brs:88) + ' $anon_309() As Dynamic (pkg:/source/rooibos/TestGroup.brs:68) + ' $anon_1ec() As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:131) + ' $anon_1eb() As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:121) + ' $anon_325(testsuite As Dynamic) As Void (pkg:/source/rooibos/TestRunner.brs:191) + ' $anon_322() As Dynamic (pkg:/source/rooibos/TestRunner.brs:72) + ' rooibos_init(testscenename As Dynamic) As Void (pkg:/source/rooibos/Rooibos.brs:27) + ' main(args As Dynamic) As Dynamic (pkg:/source/Main.brs:2) + ' + ' at (file:///Users/chris/roku/rooibos/tests/src/source/FailedAssertion.spec.bs:15) + ' + ' 2) Rooibos failed assertion tests + ' tests AssertTrue fail + ' AssertTrue with message 0: + ' + ' AssertionError: expected "false (Boolean)" to be true (Boolean) + ' + expected - actual + ' + ' -false (Boolean) + ' +true (Boolean) + ' + ' params at (file:///Users/chris/roku/rooibos/tests/src/source/FailedAssertion.spec.bs:23) + ' assertion at (file:///Users/chris/roku/rooibos/tests/src/source/FailedAssertion.spec.bs:31) + ' + sub logFailures(testGroup) + for each test in testGroup.tests + if test.result.isFail + m.failureCount++ + + resultMessage = "" + resultMessage += `${string(1, chr(9))}${m.failureCount.toStr()}) ${test.testSuite.name}\n` + resultMessage += `${string(2, chr(9))}${testGroup.name}\n` + resultMessage += `${string(3, chr(9))}${test.name}:\n\n` + if not test.result.isCrash + resultMessage += `${string(1, chr(9))}AssertionError: ${test.result.getMessage()}` + + if (test.result.actual <> "" or test.result.expected <> "") and (test.result.actual <> test.result.expected) + resultMessage += m.unifiedDiff(test.result.actual, test.result.expected) + end if + + resultMessage += chr(10) + else + resultMessage += `${string(1, chr(9))}Error: ${m.getStackTrace(test.result.error)}` + end if + + if test.isParamTest + resultMessage += `${string(1, chr(9))}params at (file://${test.testSuite.filePath.trim()}:${Rooibos.Common.AsString(test.paramLineNumber)})\n` + if test.result.lineNumber > -1 + resultMessage += `${string(1, chr(9))}assertion at (file://${test.testSuite.filePath.trim()}:${Rooibos.Common.AsString(test.result.lineNumber)})\n` + else + resultMessage += `${string(1, chr(9))}test at (file://${test.testSuite.filePath.trim()}:${Rooibos.Common.AsString(test.lineNumber)})\n` + end if + else + if test.result.lineNumber > -1 + resultMessage += `${string(1, chr(9))}at (file://${test.testSuite.filePath.trim()}:${Rooibos.Common.AsString(test.result.lineNumber)})\n` + else + resultMessage += `${string(1, chr(9))}at (file://${test.testSuite.filePath.trim()}:${Rooibos.Common.AsString(test.lineNumber)})\n` + end if + end if + print resultMessage + end if + + end for + end sub + + ' Returns a string representation of the stack trace + ' example: + ' Error: some error + ' $anon_6c() As Dynamic (pkg:/source/FailedAssertion.spec.brs:11) + ' $anon_303() As Dynamic (pkg:/source/rooibos/Test.brs:45) + ' $anon_1f2(test As Object) As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:243) + ' $anon_30a() As Dynamic (pkg:/source/rooibos/TestGroup.brs:88) + ' $anon_309() As Dynamic (pkg:/source/rooibos/TestGroup.brs:68) + ' $anon_1ec() As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:131) + ' $anon_1eb() As Dynamic (pkg:/source/rooibos/BaseTestSuite.brs:121) + ' $anon_325(testsuite As Dynamic) As Void (pkg:/source/rooibos/TestRunner.brs:191) + ' $anon_322() As Dynamic (pkg:/source/rooibos/TestRunner.brs:72) + ' rooibos_init(testscenename As Dynamic) As Void (pkg:/source/rooibos/Rooibos.brs:27) + ' main(args As Dynamic) As Dynamic (pkg:/source/Main.brs:2) + function getStackTrace(error) as string + output = `${error.message}\n` + + for i = error.backTrace.count() - 1 to 0 step -1 + e = error.backTrace[i] + ' if e.filename.instr("pkg:/source/rooibos") = -1 + output += `${string(2, chr(9))}${e["function"]} (${e.filename.trim()}:${Rooibos.Common.AsString(e.line_number)})\n` + ' end if + end for + output += chr(10) + return output + end function + + ' Returns a unified diff string based on the actual and expected string values + ' example: + ' + expected - actual + ' + ' -0 (Integer) + ' +true (Boolean) + function unifiedDiff(actual, expected) + cleanUp = function(line, m) + indent = " " + if line.left(1) = "+" + return indent + m.colorLines(rooibos.reporters.mocha.colors.diffAdded, line) + end if + if line.left(1) = "-" + return indent + m.colorLines(rooibos.reporters.mocha.colors.diffRemoved, line) + end if + if CreateObject("roRegex", "@@", "").isMatch(line) + return "--" + end if + if CreateObject("roRegex", "\\ No newline", "").isMatch(line) + return invalid + end if + return indent + line + end function + + msg = m.createPatch("string", actual, expected) + lines = msg.split(chr(10)).slice(5) + + final = chr(10) + " " + m.colorLines(rooibos.reporters.mocha.colors.diffAdded, "+ expected") + " " + m.colorLines(rooibos.reporters.mocha.colors.diffRemoved, "- actual") + final += chr(10) + chr(10) + + cleanLines = [] + for i = 0 to lines.count() - 1 + cleaned = cleanUp(lines[i], m) + if cleaned <> invalid + cleanLines.push(cleaned) + end if + end for + + return final + cleanLines.join(chr(10)) + end function + + ' Applies Asci colors to each line of a string based on the supplied color type + function colorLines(name as dynamic, targetString as string) as string + lines = targetString.split(chr(10)) + + for i = 0 to lines.count() - 1 + lines[i] = m.colors(name, lines[i]) + end for + + return lines.join(chr(10)) + end function + + ' Applies Asci colors the supplied of a string based on the supplied color type + function colors(colorType, targetString) + ' colors = { + ' pass: 90, + ' fail: 31, + ' "bright pass": 92, + ' "bright fail": 91, + ' "bright yellow": 93, + ' pending: 36, + ' suite: 0, + ' "error title": 0, + ' "error message": 31, + ' "error stack": 90, + ' checkmark: 32, + ' fast: 90, + ' medium: 33, + ' slow: 31, + ' green: 32, + ' light: 90, + ' "diff gutter": 90, + ' "diff added": 32, + ' "diff removed": 31, + ' "diff added inline": "30;42", + ' "diff removed inline": "30;41" + ' } + + if m.colorizeOutput + return chr(27) + "[" + colorType + "m" + targetString + chr(27) + "[0m" + ' return chr(27) + "[" + colors[colorType].toStr() + "m" + targetString + chr(27) + "[0m" + ' return "\u001b[" + colors[colorType].toStr() + "m" + targetString + "\u001b[0m" + else + return targetString + end if + end function + + ' Creates a patch file string based on the the differences of the two supplied strings + function createPatch(fileName, oldStr, newStr) + result = m.structuredPatch(fileName, fileName, oldStr, newStr, invalid, invalid, { + context: 4, + newlineIsToken: false + }) + if result <> invalid + return m.formatPatch(result) + end if + return invalid + end function + + ' Generate a structured patch object from two strings + function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) + if options = invalid + options = {} + end if + if options.context = invalid + options.context = 4 + end if + if options.newlineIsToken = true + throw "newlineIsToken may not be used with patch-generation functions, only with diffing functions" + end if + + return m.diffLinesResultToPatch(m.diffLines(oldStr, newStr, options), oldFileName, newFileName, oldHeader, newHeader, options) + end function + + ' Diff two sets of strings, comparing them line by line + function diffLines(oldStr, newStr, callback) + lineDiff = rooibos.reporters.mocha.new_lineDiff() + + return lineDiff.diff(oldStr, newStr, { + ignoreCase: false, + comparator: invalid, + useLongestToken: false, + oneChangePerToken: false, + maxEditLength: invalid + }) + end function + + ' Convert a diff result into a patch + function diffLinesResultToPatch(diff, oldFileName, newFileName, oldHeader, newHeader, options) + ' STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays + ' of lines containing trailing newline characters. We'll tidy up later... + + if diff = invalid + return invalid + end if + + diff.push({ value: "", lines: [] }) ' Append an empty value to make cleanup easier + + hunks = [] + oldRangeStart = 0 + newRangeStart = 0 + curRange = [] + oldLine = 1 + newLine = 1 + for i = 0 to diff.count() - 1 + current = diff[i] + if current.lines <> invalid + lines = current.lines + else + lines = m.splitLines(current.value) + end if + current.lines = lines + + if current.added = true or current.removed = true + ' If we have previous context, start with that + if not (oldRangeStart) = true + prev = diff[i - 1] + oldRangeStart = oldLine + newRangeStart = newLine + + if prev <> invalid + if options.context > 0 + curRange = m.contextLines(prev.lines.slice(-options.context)) + else + curRange = [] + end if + oldRangeStart -= curRange.count() + newRangeStart -= curRange.count() + end if + end if + + ' Output our changes + for each entry in lines + if current.added + curRange.push("+" + entry) + else + curRange.push("-" + entry) + end if + end for + + ' Track the updated file position + if current.added + newLine += lines.count() + else + oldLine += lines.count() + end if + else + ' Identical context lines. Track line changes + if oldRangeStart + ' Close out any changes that have been output (or join overlapping) + if lines.count() <= options.context * 2 and i < diff.count() - 2 + ' Overlapping + curRange.append(m.contextLines(lines)) + else + ' end the range and output + contextSize = rooibos.reporters.mocha.min(lines.count(), options.context) + curRange.append(m.contextLines(lines.slice(0, contextSize))) + + hunk = { + oldStart: oldRangeStart, + oldLines: (oldLine - oldRangeStart + contextSize), + newStart: newRangeStart, + newLines: (newLine - newRangeStart + contextSize), + lines: curRange + } + hunks.push(hunk) + + oldRangeStart = 0 + newRangeStart = 0 + curRange = [] + end if + end if + oldLine += lines.count() + newLine += lines.count() + end if + end for + + ' Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add + ' "\ No newline at end of file". + for each hunk in hunks + for i = 0 to hunk.lines.count() - 1 + if hunk.lines[i].endsWith(chr(10)) + hunk.lines[i] = hunk.lines[i].mid(0, len(hunk.lines[i]) - 1) + else + hunk.lines = rooibos.reporters.mocha.arraySplice(hunk.lines, i + 1, 0, ["\ No newline at end of file"]) + i++ ' Skip the line we just added, then continue iterating + end if + end for + end for + + return { + oldFileName: oldFileName, + newFileName: newFileName, + oldHeader: oldHeader, + newHeader: newHeader, + hunks: hunks + } + end function + + ' Split `text` into an array of lines, including the trailing newline character (where present) + function splitLines(text) + hasTrailingNl = text.endsWith(chr(10)) + result = rooibos.reporters.mocha.arrayMap(text.split(chr(10)), function(line, _ = invalid) + return line + chr(10) + end function) + if hasTrailingNl + result.pop() + else + lastEntry = result.pop() + result.push(lastEntry.mid(0, len(lastEntry) - 1)) + end if + return result + end function + + function contextLines(lines) + return rooibos.reporters.mocha.arrayMap(lines, function(entry, _ = invalid) + return " " + entry + end function) + end function + + ' Return a unified patch file contents from a structured patch + function formatPatch(diff, _ = invalid) + if type(diff) = "roArray" + return rooibos.reporters.mocha.arrayMap(diff, m.formatPatch).join(chr(10)) + end if + + ret = [] + if diff.oldFileName = diff.newFileName + ret.push("Index: " + diff.oldFileName) + end if + + ret.push("===================================================================") + if diff.oldHeader <> invalid + ret.push("--- " + diff.oldFileName + chr(9) + diff.oldHeader) + else + ret.push("--- " + diff.oldFileName) + end if + + if diff.newHeader <> invalid + ret.push("+++ " + diff.newFileName + chr(9) + diff.newHeader) + else + ret.push("+++ " + diff.newFileName) + end if + + for i = 0 to diff.hunks.count() - 1 + hunk = diff.hunks[i] + ' Unified Diff Format quirk: If the chunk size is 0, + ' the first number is one lower than one would expect. + ' https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if hunk.oldLines = 0 + hunk.oldStart -= 1 + end if + if hunk.newLines = 0 + hunk.newStart -= 1 + end if + + ret.push("@@ -" + hunk.oldStart.toStr() + "," + hunk.oldLines.toStr() + " +" + hunk.newStart.toStr() + "," + hunk.newLines.toStr() + " @@") + ret.append(hunk.lines) + end for + + return ret.join(chr(10)) + chr(10) + end function + + end class + + namespace reporters + namespace mocha + enum colors + pass = "90" + fail = "31" + brightPass = "92" + brightFail = "91" + brightYellow = "93" + pending = "36" + suite = "0" + errorTitle = "0" + errorMessage = "31" + errorStack = "90" + checkmark = "32" + fast = "90" + medium = "33" + slow = "31" + green = "32" + light = "90" + diffGutter = "90" + diffAdded = "32" + diffRemoved = "31" + diffAddedInline = "30;42" + diffRemovedInline = "30;41" + end enum + + function buildValues(diff, lastComponent, newString, oldString, useLongestToken) + ' First we convert our linked list of components in reverse order to an + ' array in the right order: + components = [] + nextComponent = invalid + while lastComponent <> invalid + components.push(lastComponent) + nextComponent = lastComponent.previousComponent + lastComponent.delete("previousComponent") + lastComponent = nextComponent + end while + components.reverse() + + componentPos = 0 + componentLen = components.count() + newPos = 0 + oldPos = 0 + + for componentPos = 0 to componentLen - 1 + component = components[componentPos] + if not component.removed + if not component.added and useLongestToken = true + value = newString.slice(newPos, newPos + component.count) + + newValue = createObject("roArray", component.count(), true) + for i = 0 to value.count() - 1 + currentValue = value[i] + oldValue = oldString[oldPos + i] + if len(oldValue) > len(currentValue) + newValue[i] = oldValue + else + newValue[i] = currentValue + end if + end for + + value = newValue + component.value = diff.join(value) + else + component.value = diff.join(newString.slice(newPos, newPos + component.count)) + end if + newPos += component.count + + ' Common case + if not component.added + oldPos += component.count + end if + else + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)) + oldPos += component.count + end if + end for + + return components + end function + + function new_Diff() + return { + ' bs:disable-next-line + diff: function(oldString, newString, options = {}) + Infinity = 2147483647 + + oldString = m.removeEmpty(m.tokenize(oldString, options)) + newString = m.removeEmpty(m.tokenize(newString, options)) + newLen = newString.count() + oldLen = oldString.count() + + editLength = 1 + maxEditLength = newLen + oldLen + if options.maxEditLength <> invalid + maxEditLength = rooibos.reporters.mocha.min(maxEditLength, options.maxEditLength) + end if + + maxExecutionTime = Infinity + abortAfterTimestamp = CreateObject("roDateTime").asSeconds() + maxExecutionTime + + bestPath = rooibos.reporters.mocha.new_objectArray() + + ' bestPath = [{ oldPos: -1, lastComponent: invalid }] + bestPath.set(0, { oldPos: -1, lastComponent: invalid }) + + ' Seed editLength = 0, i.e. the content starts with the same values + ' newPos = m.extractCommon(bestPath[0], newString, oldString, 0, options) + newPos = m.extractCommon(bestPath.get(0), newString, oldString, 0, options) + if bestPath.get(0).oldPos + 1 >= oldLen and newPos + 1 >= newLen + ' Identity per the equality and tokenizer + ' return m.done(buildValues(m, bestPath[0].lastComponent, newString, oldString, m.useLongestToken)) + return m.done(rooibos.reporters.mocha.buildValues(m, bestPath.get(0).lastComponent, newString, oldString, m.useLongestToken), options) + end if + + ' Once we hit the right edge of the edit graph on some diagonal k, we can + ' definitely reach the end of the edit graph in no more than k edits, so + ' there's no point in considering any moves to diagonal k+1 any more (from + ' which we're guaranteed to need at least k+1 more edits). + ' Similarly, once we've reached the bottom of the edit graph, there's no + ' point considering moves to lower diagonals. + ' We record this fact by setting minDiagonalToConsider and + ' maxDiagonalToConsider to some finite value once we've hit the edge of + ' the edit graph. + ' This optimization is not faithful to the original algorithm presented in + ' Myers's paper, which instead pointlessly extends D-paths off the end of + ' the edit graph - see page 7 of Myers's paper which notes this point + ' explicitly and illustrates it with a diagram. This has major performance + ' implications for some common scenarios. For instance, to compute a diff + ' where the new text simply appends d characters on the end of the + ' original text of length n, the true Myers algorithm will take O(n+d^2) + ' time while this optimization needs only O(n+d) time. + minDiagonalToConsider = -Infinity + maxDiagonalToConsider = Infinity + + ' Performs the length of edit iteration. Is a bit fugly as this has to support the + ' sync and async mode which is never fun. Loops over execEditLength until a value + ' is produced, or until the edit length exceeds options.maxEditLength (if given), + ' in which case it will return undefined. + execEditParams = { + bestPath: bestPath, + editLength: editLength, + newString: newString, + oldString: oldString, + minDiagonalToConsider: minDiagonalToConsider, + maxDiagonalToConsider: maxDiagonalToConsider, + options: options, + newLen: newLen, + oldLen: oldLen + } + while execEditParams.editLength <= maxEditLength and CreateObject("roDateTime").asSeconds() <= abortAfterTimestamp + execEdit = m.execEditLength(execEditParams) + execEditParams = execEdit + if execEdit.ret <> invalid + return execEdit.ret + end if + end while + end function + + ' Main worker method. checks all permutations of a given edit length for acceptance. + execEditLength: function(execEditParams) + startingDiagonalPath = rooibos.reporters.mocha.max(execEditParams.minDiagonalToConsider, -execEditParams.editLength) + diagonalPath = rooibos.reporters.mocha.max(execEditParams.minDiagonalToConsider, -execEditParams.editLength) + ' while diagonalPath <= min(execEditParams.maxDiagonalToConsider, execEditParams.editLength) + for diagonalPath = rooibos.reporters.mocha.max(execEditParams.minDiagonalToConsider, -execEditParams.editLength) to rooibos.reporters.mocha.min(execEditParams.maxDiagonalToConsider, execEditParams.editLength) step 2 + removePath = execEditParams.bestPath.get(diagonalPath - 1) + addPath = execEditParams.bestPath.get(diagonalPath + 1) + if removePath <> invalid + ' No one else is going to attempt to use this value, clear it + execEditParams.bestPath.set(diagonalPath - 1, invalid) + end if + + canAdd = false + if addPath <> invalid + ' what newPos will be after we do an insertion: + addPathNewPos = addPath.oldPos - diagonalPath + canAdd = addPath <> invalid and 0 <= addPathNewPos and addPathNewPos < execEditParams.newLen + end if + + canRemove = removePath <> invalid and removePath.oldPos + 1 < execEditParams.oldLen + if not canAdd and not canRemove + ' If this path is a terminal then prune + execEditParams.bestPath.set(diagonalPath, invalid) + continue for + end if + + ' Select the diagonal that we want to branch from. We select the prior + ' path whose position in the old string is the farthest from the origin + ' and does not pass the bounds of the diff graph + if not canRemove or (canAdd and removePath.oldPos < addPath.oldPos) + basePath = m.addToPath(addPath, true, false, 0, execEditParams.options) + else + basePath = m.addToPath(removePath, false, true, 1, execEditParams.options) + end if + + newPos = m.extractCommon(basePath, execEditParams.newString, execEditParams.oldString, diagonalPath, execEditParams.options) + + if basePath.oldPos + 1 >= execEditParams.oldLen and newPos + 1 >= execEditParams.newLen + ' If we have hit the end of both strings, then we are done + execEditParams.ret = m.done(rooibos.reporters.mocha.buildValues(m, basePath.lastComponent, execEditParams.newString, execEditParams.oldString, m.useLongestToken), execEditParams.options) + return execEditParams + else + execEditParams.bestPath.set(diagonalPath, basePath) + if basePath.oldPos + 1 >= execEditParams.oldLen + execEditParams.maxDiagonalToConsider = rooibos.reporters.mocha.min(execEditParams.maxDiagonalToConsider, diagonalPath - 1) + end if + if newPos + 1 >= execEditParams.newLen + execEditParams.minDiagonalToConsider = rooibos.reporters.mocha.max(execEditParams.minDiagonalToConsider, diagonalPath + 1) + end if + end if + end for + + execEditParams.editLength++ + return execEditParams + end function + + addToPath: function(path, added, removed, oldPosInc, options) + last = path.lastComponent + if last <> invalid and not options.oneChangePerToken and last.added = added and last.removed = removed + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent } + } + else + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { count: 1, added: added, removed: removed, previousComponent: last } + } + end if + end function + + extractCommon: function(basePath, newString, oldString, diagonalPath, options) + newLen = newString.count() + oldLen = oldString.count() + oldPos = basePath.oldPos + newPos = oldPos - diagonalPath + + commonCount = 0 + while newPos + 1 < newLen and oldPos + 1 < oldLen and m.equals(oldString[oldPos + 1], newString[newPos + 1], options) + newPos++ + oldPos++ + commonCount++ + if options.oneChangePerToken + basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false } + end if + end while + + if commonCount and not options.oneChangePerToken + basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false } + end if + + basePath.oldPos = oldPos + return newPos + end function + + equals: function(left, right, options) + if options.comparator <> invalid + return options.comparator(left, right) + else + return left = right or (options.ignoreCase = true and lCase(left) = lCase(right)) + end if + end function + + removeEmpty: function(array) + ret = [] + for i = 0 to array.count() - 1 + if array[i] <> "" + ret.push(array[i]) + end if + end for + return ret + end function + + tokenize: function(value, options) + return value.split("") + end function + + join: function(chars) + return chars.join("") + end function + + postProcess: function(changeObjects, options) + return changeObjects + end function + + done: function(value, options) + value = m.postProcess(value, options) + return value + end function + } + end function + + function arrayMap(arr as object, callback as function) as object + if type(arr) <> "roArray" + print "Error: First argument must be an array." + return invalid + end if + + if type(callback) <> "Function" + print "Error: Second argument must be a function." + return invalid + end if + + ' Create a new array to store the results + result = [] + for each item in arr + ' Apply the callback function to the item + transformedItem = callback(item) + result.Push(transformedItem) + end for + + return result + end function + + function min(a as dynamic, b as dynamic) as dynamic + if a < b + return a + else + return b + end if + end function + + function max(a as dynamic, b as dynamic) as dynamic + if a > b + return a + else + return b + end if + end function + + function arraySplice(array, start, deleteCount, items = []) + partOne = array.slice(0, start) + partTwo = array.slice(start + deleteCount) + + if items <> invalid + partOne.append(items) + end if + + partOne.append(partTwo) + return partOne + end function + + function new_lineDiff() + lineDiff = rooibos.reporters.mocha.new_Diff() + lineDiff.tokenize = function(value, options) + if options.stripTrailingCr = true + ' remove one \r before \n to match GNU diff's --strip-trailing-cr behavior + value = CreateObject("roRegex", "\r\n", "g").ReplaceAll(value, chr(10)) + end if + + retLines = [] + linesAndNewlines = CreateObject("roRegex", "(\n|\r\n)", "g").split(value).toArray() + + ' Ignore the final empty token that occurs if the string ends with a new line + if linesAndNewlines[linesAndNewlines.count() - 1] = "" + linesAndNewlines.pop() + end if + + ' Add the newlines back that where stripped out by the split + for i = 0 to linesAndNewlines.count() - 2 + linesAndNewlines[i] = linesAndNewlines[i] + chr(10) + end for + + ' Merge the content and line separators into single tokens + ' for i = 0 to linesAndNewlines.count() - 1 + ' line = linesAndNewlines[i] + + ' if i mod 2 and not options.newlineIsToken = true + ' retLines[retLines.count() - 1] = retLines[retLines.count() - 1] + line + ' else + ' retLines.push(line) + ' end if + ' end for + + return linesAndNewlines + end function + + lineDiff.equals = function(leftPart, rightPart, options) + ' If we're ignoring whitespace, we need to normalise lines by stripping + ' whitespace before checking equality. (This has an annoying interaction + ' with newlineIsToken that requires special handling: if newlines get their + ' own token, then we DON'T want to trim the *newline* tokens down to empty + ' strings, since this would cause us to treat whitespace-only line content + ' as equal to a separator between lines, which would be weird and + ' inconsistent with the documented behavior of the options.) + if options.ignoreWhitespace = true + if not options.newlineIsToken = true or leftPart.inStr(0, chr(10)) > -1 + leftPart = leftPart.trim() + end if + if not options.newlineIsToken = true or rightPart.inStr(0, chr(10)) > -1 + rightPart = rightPart.trim() + end if + else if options.ignoreNewlineAtEof = true and not options.newlineIsToken = true + if leftPart.endsWith(chr(10)) + leftPart = leftPart.mid(0, len(leftPart) - 1) + end if + if rightPart.endsWith(chr(10)) + rightPart = rightPart.mid(0, len(rightPart) - 1) + end if + end if + return rooibos.reporters.mocha.new_Diff().equals(leftPart, rightPart, options) + end function + + return lineDiff + end function + + function new_objectArray() + return { + array: [] + aa: {} + + count: function() + return m.array.count() + end function + + set: sub(index, value) + if index >= 0 + m.array[index] = value + else + m.aa[index.toStr()] = value + end if + end sub + + get: function(index) + if index >= 0 + return m.array[index] + else + return m.aa[index.toStr()] + end if + end function + } + end function + + end namespace + end namespace +end namespace diff --git a/framework/src/source/rooibos/Rooibos.bs b/framework/src/source/rooibos/Rooibos.bs index 03b1108e..09890319 100644 --- a/framework/src/source/rooibos/Rooibos.bs +++ b/framework/src/source/rooibos/Rooibos.bs @@ -1,7 +1,7 @@ namespace rooibos function init(testSceneName = invalid) as void if createObject("roAPPInfo").IsDev() <> true - ? " not running in dev mode! - rooibos tests only support sideloaded builds - aborting" + rooibos.common.logError(" not running in dev mode! - rooibos tests only support sideloaded builds - aborting") return end if @@ -12,7 +12,7 @@ namespace rooibos testSceneName = "RooibosScene" end if - ? "Starting test using test scene with name RooibosScene" ; testSceneName + rooibos.common.logInfo(`Starting test using test scene with name RooibosScene ${testSceneName}`) scene = screen.CreateScene(testSceneName) scene.id = "ROOT" screen.show() @@ -24,16 +24,17 @@ namespace rooibos }) if scene.hasField("isReadyToStartTests") and scene.isReadyToStartTests = false - ? "The scene is not ready yet - waiting for it to set isReadyToStartTests to true" + rooibos.common.logInfo("The scene is not ready yet - waiting for it to set isReadyToStartTests to true") scene.observeField("isReadyToStartTests", m.port) else - ? "scene is ready; running tests now" + rooibos.common.logInfo("scene is ready; running tests now") + print "" runner = new rooibos.TestRunner(scene, m) runner.Run() if runner.config.keepAppOpen = false - ? "keepAppOpen is false; exiting Rooibos" + rooibos.common.logInfo("keepAppOpen is false; exiting Rooibos") ' End statement will also exit the caller of this function ' leading to an instant exit of the application @@ -53,7 +54,8 @@ namespace rooibos else if msgType = "roSGNodeEvent" if msg.getField() = "isReadyToStartTests" and msg.getData() = true - ? "scene is ready; running tests now" + rooibos.common.logInfo("scene is ready; running tests now") + print "" runner = new rooibos.TestRunner(scene, m) runner.Run() end if diff --git a/framework/src/source/rooibos/Stats.bs b/framework/src/source/rooibos/Stats.bs index 3d940926..c5f4834b 100644 --- a/framework/src/source/rooibos/Stats.bs +++ b/framework/src/source/rooibos/Stats.bs @@ -31,6 +31,9 @@ namespace rooibos m.crashedCount++ else if result.isFail m.failedCount++ + else if result.isSkipped + m.ignoredTestNames.push(result.test.name) + m.ignoredCount++ else m.passedCount++ end if diff --git a/framework/src/source/rooibos/Test.bs b/framework/src/source/rooibos/Test.bs index f9af81b7..8faf88bf 100644 --- a/framework/src/source/rooibos/Test.bs +++ b/framework/src/source/rooibos/Test.bs @@ -28,6 +28,7 @@ namespace rooibos m.isIgnored = data.isIgnored m.isAsync = data.isAsync m.asyncTimeout = data.asyncTimeout + m.slow = data.slow m.name = data.name m.lineNumber = data.lineNumber m.paramLineNumber = data.paramLineNumber diff --git a/framework/src/source/rooibos/TestGroup.bs b/framework/src/source/rooibos/TestGroup.bs index f45098e3..eacf6e40 100644 --- a/framework/src/source/rooibos/TestGroup.bs +++ b/framework/src/source/rooibos/TestGroup.bs @@ -59,16 +59,19 @@ namespace rooibos private currentTestIndex = 0 'TODO CONVERT THIS TO ASYNC function run() - ' ? ">>>>>>>>>>>>" - ' ? " RUNNING TEST GROUP" + rooibos.common.logTrace(">>>>>>>>>>>>") + rooibos.common.logTrace("RUNNING TEST GROUP") m.testRunner = m.testSuite.testRunner + m.notifyReportersOnTestGroupBegin() m.testSuite.setTestTimer(0) if m.testSuite.isAsync = true - ' ? " THIS GROUP IS ASYNC" + rooibos.common.logTrace("THIS GROUP IS ASYNC") m.runAsync() + return false else - ' ? " THIS GROUP IS SYNC" + rooibos.common.logTrace("THIS GROUP IS SYNC") m.runSync() + return true end if end function @@ -80,10 +83,20 @@ namespace rooibos test = new rooibos.Test(m, testData) m.tests.push(test) + if test.isIgnored + m.notifyReportersOnTestBegin(test) + m.testSuite.runTest(test) + m.notifyReportersOnTestComplete(test) + m.stats.appendTestResult(test.result) + continue for + end if + isOk = m.runSuiteFunction(m.beforeEachFunctionName, "beforeEach", test) if isOk + m.notifyReportersOnTestBegin(test) m.testSuite.runTest(test) + m.notifyReportersOnTestComplete(test) end if m.runSuiteFunction(m.afterEachFunctionName, "afterEach", test) @@ -91,13 +104,14 @@ namespace rooibos m.stats.appendTestResult(test.result) if m.stats.hasFailures and m.testSuite.isFailingFast - ? "Terminating group due to failed test" + rooibos.common.logTrace("Terminating group due to failed test") exit for end if end for else - ? "ERROR running test setup function" + rooibos.common.logError("ERROR running test setup function") end if + m.notifyReportersOnTestGroupComplete() m.runSuiteFunction(m.tearDownFunctionName, "tearDown") end function @@ -115,29 +129,37 @@ namespace rooibos m.currentTestIndex = -1 m.runNextAsync() else - ? "ERROR running test setup function" + rooibos.common.logError("ERROR running test setup function") m.runSuiteFunction(m.tearDownFunctionName, "tearDown") end if end function private function runNextAsync() - ' ? "Getting next async test" + rooibos.common.logTrace("Getting next async test") m.currentTestIndex++ m.currentTest = m.tests[m.currentTestIndex] m.testSuite.isDoneCalled = false m.testTimer.mark() if m.currentTest = invalid - ' ? " all tests are finished" - 'finished + rooibos.common.logTrace("All tests are finished") m.finishAsyncTests() else + test = m.currentTest + if test.isIgnored + m.notifyReportersOnTestBegin(test) + m.testSuite.runTest(test) + m.testRunner.top.rooibosTestFinished = true + m.onAsyncTestComplete() + return invalid + end if + m.testRunner.top.rooibosTestFinished = false isOk = m.runSuiteFunction(m.beforeEachFunctionName, "beforeEach", m.currentTest) if isOk 'TODO - set a timeout here - test = m.currentTest if test.isAsync <> true - ? "Executing test synchronously" + rooibos.common.logDebug("Executing test synchronously") + m.notifyReportersOnTestBegin(test) m.testSuite.runTest(test) m.testRunner.top.rooibosTestFinished = true m.onAsyncTestComplete() @@ -146,10 +168,11 @@ namespace rooibos if test.isAsync = true m.testSuite.setTestTimer(test.asyncTimeout) end if + m.notifyReportersOnTestBegin(test) m.testSuite.runTest(test) if test.result.isFail - ? "Test failed before any async code could be executed" + rooibos.common.logTrace("Test failed before any async code could be executed") m.testRunner.top.unobserveFieldScoped("rooibosTestFinished") m.testRunner.top.rooibosTestFinished = true m.onAsyncTestComplete() @@ -157,7 +180,7 @@ namespace rooibos end if else - ? "Error running test before each function" + rooibos.common.logTrace("Error running test before each function") m.isTestFailedDueToEarlyExit = true m.finishAsyncTests() end if @@ -165,7 +188,8 @@ namespace rooibos end function private function onAsyncTestComplete() - ' ? "++ CURRENT TEST COMPLETED" + rooibos.common.logTrace("++ CURRENT TEST COMPLETED") + m.notifyReportersOnTestComplete(m.currentTest) m.runSuiteFunction(m.afterEachFunctionName, "afterEach", m.currentTest) m.testRunner.top.unobserveFieldScoped("rooibosTestFinished") @@ -174,7 +198,7 @@ namespace rooibos m.stats.appendTestResult(m.currentTest.result) if m.stats.hasFailures and m.testSuite.isFailingFast - ? "Terminating group due to failed test" + rooibos.common.logTrace("Terminating group due to failed test") m.isTestFailedDueToEarlyExit = true m.finishAsyncTests() else @@ -187,12 +211,13 @@ namespace rooibos m.testRunner.top.unobserveFieldScoped("rooibosTestFinished") m.testSuite.setTestTimer(0) m.runSuiteFunction(m.tearDownFunctionName, "tearDown") - ' ? " indicating test suite is done" + rooibos.common.logTrace("Indicating test suite is done") ' m.testRunner.top.addFields({ asyncRooibosTestResult: { ' stats: m.stats ' tests: m.tests ' } ' }) + m.notifyReportersOnTestGroupComplete() m.testSuite.testGroupDone() end function @@ -220,10 +245,41 @@ namespace rooibos return false end function + private sub notifyReportersOnTestGroupBegin() + for each reporter in m.testSuite.testReporters + if rooibos.common.isFunction(reporter.onTestGroupBegin) + reporter.onTestGroupBegin({ group: m }) + end if + end for + end sub + + private sub notifyReportersOnTestBegin(test as rooibos.Test) + for each reporter in m.testSuite.testReporters + if rooibos.common.isFunction(reporter.onTestBegin) + reporter.onTestBegin({ test: test }) + end if + end for + end sub + + private sub notifyReportersOnTestComplete(test as rooibos.Test) + for each reporter in m.testSuite.testReporters + if rooibos.common.isFunction(reporter.onTestComplete) + reporter.onTestComplete({ test: test }) + end if + end for + end sub + + private sub notifyReportersOnTestGroupComplete() + for each reporter in m.testSuite.testReporters + if rooibos.common.isFunction(reporter.onTestGroupComplete) + reporter.onTestGroupComplete({ group: m }) + end if + end for + end sub end class function onTestComplete() - ' ? "++++++++ THE TEST COMPLETED" + rooibos.common.logTrace("++++++++ THE TEST COMPLETED") m.testRunner.currentGroup.onAsyncTestComplete() end function end namespace \ No newline at end of file diff --git a/framework/src/source/rooibos/TestResult.bs b/framework/src/source/rooibos/TestResult.bs index 42d5ba18..7fae892c 100644 --- a/framework/src/source/rooibos/TestResult.bs +++ b/framework/src/source/rooibos/TestResult.bs @@ -3,6 +3,9 @@ namespace rooibos public isFail = false public isCrash = false + public isSkipped = false + public actual = invalid + public expected = invalid public message = "" public lineNumber = -1 public test = invalid @@ -17,6 +20,8 @@ namespace rooibos function merge(other) m.isFail = other.isFail m.isCrash = other.isCrash + m.actual = other.actual + m.expected = other.expected m.message = other.message m.lineNumber = other.lineNumber m.time = other.time @@ -31,12 +36,14 @@ namespace rooibos m.lineNumber = -1 end function - function fail(message as string, lineNumber = -1) + function fail(message as string, lineNumber = -1, actual = "", expected = "") if message <> "" and not m.isFail if not m.isFail m.lineNumber = lineNumber m.isFail = true m.message = message + m.actual = actual + m.expected = expected end if end if if m.throwOnFailedAssertion @@ -44,6 +51,15 @@ namespace rooibos end if end function + function skip(message as string) + if message <> "" and not m.isFail + if not m.isFail + m.isSkipped = true + m.message = message + end if + end if + end function + function crash(message as string, error) if message <> "" and not m.isCrash if not m.isCrash @@ -78,6 +94,8 @@ namespace rooibos return "CRASH" else if m.isFail return "FAIL" + else if m.isSkipped + return "SKIP" else return "PASS" end if diff --git a/framework/src/source/rooibos/TestRunner.bs b/framework/src/source/rooibos/TestRunner.bs index adffa227..fc52e2ba 100644 --- a/framework/src/source/rooibos/TestRunner.bs +++ b/framework/src/source/rooibos/TestRunner.bs @@ -47,7 +47,7 @@ namespace rooibos for each reporter in m.testReporters if rooibos.common.isFunction(reporter.onBegin) - reporter.onBegin({}) + reporter.onBegin({ runner: m }) end if end for @@ -66,6 +66,8 @@ namespace rooibos testSuite = invalid if suiteClass <> invalid testSuite = suiteClass() + testSuite.testRunner = m + testSuite.testReporters = m.testReporters testSuite.global = m.nodeContext.global testSuite.context = m.nodeContext testSuite.top = m.nodeContext.top @@ -89,7 +91,7 @@ namespace rooibos testSuite.scene.failedText = "Failed Suites: " + chr(10) + failedText end if else - ? "ERROR! could not create test for suite : "; name + rooibos.common.logError(`Could not create test for suite : ${name}`) failedText = "COULD NOT CREATE suite " + name + chr(10) + failedText testSuite.scene.failedText = "Failed Suites: " + chr(10) + failedText end if @@ -124,8 +126,15 @@ namespace rooibos rooibos.Coverage.printLCovInfo() end if else - ? "rooibos.Coverage.reportCodeCoverage is not a function" + rooibos.common.logDebug("rooibos.Coverage.reportCodeCoverage is not a function") end if + + ' Final results to be logged after all test reporters have finished + resultStatus = "PASS" + if isFailed then resultStatus = "FAIL" + print "[Rooibos Result]: " + resultStatus + print "[Rooibos Shutdown]" + if m.config.sendHomeOnFinish <> false m.sendHomeKeypress() end if @@ -138,6 +147,7 @@ namespace rooibos if suiteClass <> invalid testSuite = suiteClass() + testSuite.testReporters = m.testReporters testSuite.global = m.nodeContext.global testSuite.node = m.nodeContext testSuite.top = m.nodeContext.top @@ -160,21 +170,21 @@ namespace rooibos m.currentTestSuite = testSuite testSuite.testRunner = m if testSuite.isAsync = true - ? "Running suite asynchronously!" + rooibos.common.logDebug("Running suite asynchronously!") m.nodeContext.top.observeFieldScoped("rooibosSuiteFinished", "Rooibos_onTestSuiteComplete") testSuite.run() return invalid else - ? "Running suite synchronously!" + rooibos.common.logDebug("Running suite synchronously!") testSuite.run() return m.onTestSuiteComplete() end if else - ? "[ERROR] could not create test suite " ; nodeTestName + rooibos.common.logError(`Could not create test suite ${nodeTestName}`) end if - ? "ERROR! (runInNodeMode) executing node test " + nodeTestName + " was unsuccessful." + rooibos.common.logError(`(runInNodeMode) executing node test ${nodeTestName} was unsuccessful.`) return invalid end function @@ -188,17 +198,15 @@ namespace rooibos } else - ? "[ERROR] could not create test suite " ; m.testRunner.nodeTestName + rooibos.common.logError(`could not create test suite ${m.testRunner.nodeTestName}`) end if - ? "ERROR! (onTestSuiteComplete) executing node test " + m.testRunner.nodeTestName + " was unsuccessful." + rooibos.common.logError(`(onTestSuiteComplete) executing node test ${m.testRunner.nodeTestName} was unsuccessful.`) return invalid end function private function runTestSuite(testSuite) as void if testSuite.groupsData <> invalid and testSuite.groupsData.count() > 0 - ? "" - ? rooibos.common.fillText("> SUITE: " + testSuite.name, ">", 80) m.testSuites.push(testSuite) if testSuite.isNodeTest @@ -225,7 +233,7 @@ namespace rooibos if attempts = maxAttempts return false end if - ' ? "waiting for signal field '" ; fieldName "' - " ; attempts " VALUE " target[fieldName] + rooibos.common.logTrace(`waiting for signal field '${fieldName}' - ${attempts} VALUE ${target[fieldName]}`) end while return true @@ -238,8 +246,7 @@ namespace rooibos private function runNodeTest(testSuite) as void if testSuite.generatedNodeName <> "" - ? " +++++RUNNING NODE TEST" - ? " node type is " ; testSuite.generatedNodeName + rooibos.common.logDebug(`+++++RUNNING NODE TEST${chr(10)}node type is ${testSuite.generatedNodeName}`) node = m.testScene.createChild(testSuite.generatedNodeName) 'wait on the field @@ -256,7 +263,7 @@ namespace rooibos if node.rooibosSuiteFinished = false timeout = testSuite.asyncTimeout = invalid ? 60000 : testSuite.asyncTimeout - ? "Waiting max " timeout "ms for the test suite to finish" + rooibos.common.logDebug(`Waiting max ${timeout}ms for the test suite to finish`) t = createObject("roTimespan") while node.rooibosSuiteFinished = false m.wait(10) @@ -271,19 +278,17 @@ namespace rooibos testSuite.stats.merge(nodeResults.stats) m.mergeGroups(testSuite, nodeResults.groups) else - ? " ERROR! The node test"; testSuite.name; " did not indicate test completion. Did you call m.done() in your test? Did you correctly configure your node test? Please refer to : https://github.com/rokucommunity/rooibos/blob/master/docs/index.md#testing-scenegraph-nodes" + rooibos.common.logError(`The node test ${testSuite.name} did not indicate test completion. Did you call m.done() in your test? Did you correctly configure your node test? Please refer to : https://github.com/rokucommunity/rooibos/blob/master/docs/index.md#testing-scenegraph-nodes`) end if m.testScene.removeChild(node) return else - ? " ERROR!! - could not create node required to execute tests for " ; testSuite.name - ? " Node of type " ; testSuite.generatedNodeName ; " was not found/could not be instantiated" + rooibos.common.logError(`Could not create node required to execute tests for ${testSuite.name}${chr(10)}Node of type ${testSuite.generatedNodeName} was not found/could not be instantiated`) end if else - ? " ERROR!! - could not create node required to execute tests for " ; testSuite.name - ? " No node type was provided" + rooibos.common.logError(`Could not create node required to execute tests for ${testSuite.name}${chr(10)}No node type was provided`) end if testSuite.stats.hasFailures = true @@ -315,16 +320,16 @@ namespace rooibos end function private function getTestReporters() - reporters = [] + testReporters = [] for each factory in m.config.reporters if rooibos.common.isFunction(factory) - reporters.push(factory(m)) + testReporters.push(factory(m)) end if end for - if reporters.isEmpty() - reporters.push(new rooibos.ConsoleTestReporter(m)) + if testReporters.isEmpty() + testReporters.push(new rooibos.ConsoleTestReporter(m)) end if - return reporters + return testReporters end function end class @@ -340,7 +345,7 @@ namespace rooibos m.top.AppendChild(node) return node else - ? " Error creating test node of type " ; nodeType + rooibos.common.logError(`Error creating test node of type ${nodeType}`) return invalid end if end function diff --git a/package-lock.json b/package-lock.json index 588c828e..c93bfb87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@rokucommunity/bslint": "^0.5.0", - "brighterscript": "^0.67.4", + "brighterscript": "^0.68.3", "ropm": "^0.5.0" } }, @@ -316,6 +316,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "dev": true + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -338,12 +344,30 @@ "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", "dev": true }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dev": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "node_modules/@xml-tools/ast": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@xml-tools/ast/-/ast-5.0.5.tgz", @@ -506,9 +530,9 @@ } }, "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, "node_modules/balanced-match": { @@ -584,9 +608,9 @@ } }, "node_modules/brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.68.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.68.3.tgz", + "integrity": "sha512-Fy9/QRsGdqZayWuho4oW8cknP3ZnOHL2l7uWn/AEJfiikwlhgz5jZu1oQV1iyjBQBLpgqIsqk9fHWqRybs3/8A==", "dev": true, "dependencies": { "@rokucommunity/bslib": "^0.1.1", @@ -612,7 +636,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.12.1", + "roku-deploy": "^3.12.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "^9.0.1", @@ -1105,6 +1129,41 @@ "node": "*" } }, + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -1835,9 +1894,9 @@ } }, "node_modules/postman-request": { - "version": "2.88.1-postman.39", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.39.tgz", - "integrity": "sha512-rsncxxDlbn1YpygXSgJqbJzIjGlHFcZjbYDzeBPTQHMDfLuSTzZz735JHV8i1+lOROuJ7MjNap4eaSD3UijHzQ==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "dev": true, "dependencies": { "@postman/form-data": "~3.1.1", @@ -1874,10 +1933,16 @@ "dev": true }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -2021,11 +2086,12 @@ } }, "node_modules/roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "dev": true, "dependencies": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -2038,7 +2104,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -2923,6 +2989,12 @@ } } }, + "@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "dev": true + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -2945,12 +3017,30 @@ "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", "dev": true }, + "@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "@xml-tools/ast": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/@xml-tools/ast/-/ast-5.0.5.tgz", @@ -3084,9 +3174,9 @@ "dev": true }, "aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, "balanced-match": { @@ -3142,9 +3232,9 @@ } }, "brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.68.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.68.3.tgz", + "integrity": "sha512-Fy9/QRsGdqZayWuho4oW8cknP3ZnOHL2l7uWn/AEJfiikwlhgz5jZu1oQV1iyjBQBLpgqIsqk9fHWqRybs3/8A==", "dev": true, "requires": { "@rokucommunity/bslib": "^0.1.1", @@ -3170,7 +3260,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.12.1", + "roku-deploy": "^3.12.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "^9.0.1", @@ -3569,6 +3659,26 @@ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true }, + "form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -4140,9 +4250,9 @@ "dev": true }, "postman-request": { - "version": "2.88.1-postman.39", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.39.tgz", - "integrity": "sha512-rsncxxDlbn1YpygXSgJqbJzIjGlHFcZjbYDzeBPTQHMDfLuSTzZz735JHV8i1+lOROuJ7MjNap4eaSD3UijHzQ==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "dev": true, "requires": { "@postman/form-data": "~3.1.1", @@ -4176,10 +4286,13 @@ "dev": true }, "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } }, "punycode": { "version": "2.3.1", @@ -4287,11 +4400,12 @@ } }, "roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "dev": true, "requires": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -4304,7 +4418,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, diff --git a/package.json b/package.json index 748643e1..814507a8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ ], "devDependencies": { "@rokucommunity/bslint": "^0.5.0", - "brighterscript": "^0.67.4", + "brighterscript": "^0.68.3", "ropm": "^0.5.0" }, "repository": { diff --git a/tests/bsconfig.json b/tests/bsconfig.json index 06b94a1d..d313e376 100644 --- a/tests/bsconfig.json +++ b/tests/bsconfig.json @@ -17,7 +17,7 @@ ] }, 1128, - 1001, + 1001, 1140 ], "plugins": [ @@ -28,13 +28,17 @@ ], "rooibos": { "showOnlyFailures": true, - "catchCrashes": false, + "catchCrashes": true, + "colorizeOutput": true, "lineWidth": 70, "failFast": false, "sendHomeOnFinish": false, "keepAppOpen": true, "isGlobalMethodMockingEnabled": true, - "isRecordingCodeCoverage": false + "isRecordingCodeCoverage": false, + "reporters": [ + "mocha" + ] }, "sourceMap": true, "retainStagingDir": true diff --git a/tests/package-lock.json b/tests/package-lock.json index 100a4dd5..7e7f53c7 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -9,15 +9,15 @@ "version": "4.1.1", "license": "MIT", "devDependencies": { - "brighterscript": "^0.67.4", - "rooibos": "../bsc-plugin", + "brighterscript": "^0.68.3", + "rooibos-roku": "../bsc-plugin", "ts-node": "^10.7.0", "typescript": "^4.6.4" } }, "../bsc-plugin": { "name": "rooibos-roku", - "version": "5.12.0", + "version": "5.14.0", "dev": true, "license": "ISC", "dependencies": { @@ -26,7 +26,7 @@ "source-map": "^0.7.3", "undent": "^0.1.0", "vscode-languageserver": "~6.1.1", - "vscode-languageserver-protocol": "~3.15.3", + "vscode-languageserver-protocol": "~3.17.5", "yargs": "^16.2.0" }, "bin": { @@ -41,7 +41,7 @@ "@types/yargs": "^15.0.5", "@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/parser": "^5.27.0", - "brighterscript": "^0.67.4", + "brighterscript": "^0.68.3", "chai": "^4.2.0", "chai-subset": "^1.6.0", "coveralls": "^3.0.0", @@ -369,16 +369,39 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "dev": true + }, "node_modules/@types/node": { "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, - "peer": true, "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dev": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "node_modules/@xml-tools/parser": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", @@ -517,9 +540,9 @@ } }, "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, "node_modules/balanced-match": { @@ -595,9 +618,9 @@ } }, "node_modules/brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.68.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.68.3.tgz", + "integrity": "sha512-Fy9/QRsGdqZayWuho4oW8cknP3ZnOHL2l7uWn/AEJfiikwlhgz5jZu1oQV1iyjBQBLpgqIsqk9fHWqRybs3/8A==", "dev": true, "dependencies": { "@rokucommunity/bslib": "^0.1.1", @@ -623,7 +646,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.12.1", + "roku-deploy": "^3.12.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "^9.0.1", @@ -903,16 +926,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -925,9 +948,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -963,6 +986,21 @@ "node": "*" } }, + "node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -1418,9 +1456,9 @@ } }, "node_modules/postman-request": { - "version": "2.88.1-postman.39", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.39.tgz", - "integrity": "sha512-rsncxxDlbn1YpygXSgJqbJzIjGlHFcZjbYDzeBPTQHMDfLuSTzZz735JHV8i1+lOROuJ7MjNap4eaSD3UijHzQ==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "dev": true, "dependencies": { "@postman/form-data": "~3.1.1", @@ -1457,10 +1495,16 @@ "dev": true }, "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -1589,11 +1633,12 @@ } }, "node_modules/roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "dev": true, "dependencies": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -1606,7 +1651,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -1628,7 +1673,7 @@ "node": ">=6 <7 || >=8" } }, - "node_modules/rooibos": { + "node_modules/rooibos-roku": { "resolved": "../bsc-plugin", "link": true }, @@ -1909,8 +1954,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/universalify": { "version": "0.1.2", @@ -2392,16 +2436,39 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "dev": true + }, "@types/node": { "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, - "peer": true, "requires": { "undici-types": "~5.26.4" } }, + "@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "@xml-tools/parser": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", @@ -2511,9 +2578,9 @@ "dev": true }, "aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, "balanced-match": { @@ -2569,9 +2636,9 @@ } }, "brighterscript": { - "version": "0.67.5", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.67.5.tgz", - "integrity": "sha512-OUJ5xLymzKXir8/RdWKamRIYbcqFy2JgqSp6eu2F+wdlCp3rlKCrP83pjEJ9kuqwJCo/V2pjTUcilcNBLkvuIg==", + "version": "0.68.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.68.3.tgz", + "integrity": "sha512-Fy9/QRsGdqZayWuho4oW8cknP3ZnOHL2l7uWn/AEJfiikwlhgz5jZu1oQV1iyjBQBLpgqIsqk9fHWqRybs3/8A==", "dev": true, "requires": { "@rokucommunity/bslib": "^0.1.1", @@ -2597,7 +2664,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.12.1", + "roku-deploy": "^3.12.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "^9.0.1", @@ -2826,16 +2893,16 @@ "dev": true }, "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" } }, "fast-json-stable-stringify": { @@ -2845,9 +2912,9 @@ "dev": true }, "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -2874,6 +2941,18 @@ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true }, + "form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + } + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -3231,9 +3310,9 @@ "dev": true }, "postman-request": { - "version": "2.88.1-postman.39", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.39.tgz", - "integrity": "sha512-rsncxxDlbn1YpygXSgJqbJzIjGlHFcZjbYDzeBPTQHMDfLuSTzZz735JHV8i1+lOROuJ7MjNap4eaSD3UijHzQ==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "dev": true, "requires": { "@postman/form-data": "~3.1.1", @@ -3267,10 +3346,13 @@ "dev": true }, "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } }, "punycode": { "version": "2.3.1", @@ -3371,11 +3453,12 @@ "dev": true }, "roku-deploy": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz", - "integrity": "sha512-PEOdiFKGW1jrcoC9zjb+9kOWC/Q3eH7dzPvSi5kKignjNcyDiyJi+/wINwNrzw9SsxAVtw+NLvYueyZi9wQVsw==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.3.tgz", + "integrity": "sha512-4gkNW/N4VeP4hKFMNUzMS9tCXm67OdLaJIPj9kBjf81Nqn7EPHfdYLVAa9sQbRV0tSNDUUM7qk5/bSLC1G653A==", "dev": true, "requires": { + "@types/request": "^2.47.0", "chalk": "^2.4.2", "dateformat": "^3.0.3", "dayjs": "^1.11.0", @@ -3388,7 +3471,7 @@ "micromatch": "^4.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", - "postman-request": "^2.88.1-postman.32", + "postman-request": "^2.88.1-postman.40", "temp-dir": "^2.0.0", "xml2js": "^0.5.0" }, @@ -3406,7 +3489,7 @@ } } }, - "rooibos": { + "rooibos-roku": { "version": "file:../bsc-plugin", "requires": { "@types/chai": "^4.1.2", @@ -3417,7 +3500,7 @@ "@types/yargs": "^15.0.5", "@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/parser": "^5.27.0", - "brighterscript": "^0.67.4", + "brighterscript": "^0.68.3", "chai": "^4.2.0", "chai-subset": "^1.6.0", "coveralls": "^3.0.0", @@ -3438,7 +3521,7 @@ "typescript": "^4.9.5", "undent": "^0.1.0", "vscode-languageserver": "~6.1.1", - "vscode-languageserver-protocol": "~3.15.3", + "vscode-languageserver-protocol": "~3.17.5", "yargs": "^16.2.0" } }, @@ -3626,8 +3709,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "peer": true + "dev": true }, "universalify": { "version": "0.1.2", diff --git a/tests/package.json b/tests/package.json index bd211c4d..7e37e6c8 100644 --- a/tests/package.json +++ b/tests/package.json @@ -8,10 +8,10 @@ "test": "source ./.vscode/.env && npx rooibos --project=bsconfig.json --host=$ROKU_DEV_TARGET --password=$ROKU_DEVPASSWORD" }, "devDependencies": { - "brighterscript": "^0.67.4", + "brighterscript": "^0.68.3", + "rooibos-roku": "../bsc-plugin", "ts-node": "^10.7.0", - "typescript": "^4.6.4", - "rooibos-roku": "../bsc-plugin" + "typescript": "^4.6.4" }, "repository": { "type": "git", diff --git a/tests/src/source/Assertion.spec.bs b/tests/src/source/Assertion.spec.bs index 3557c7d4..507210be 100644 --- a/tests/src/source/Assertion.spec.bs +++ b/tests/src/source/Assertion.spec.bs @@ -39,6 +39,10 @@ namespace tests m.assertTrue(isFail) end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertTrue tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("AssertTrue") @params(true, true) @params(false, false) @@ -47,15 +51,15 @@ namespace tests @params(1, false) @params("test", false) function _(value, expectedAssertResult) - m.assertTrue(value) isFail = m.currentResult.isFail m.currentResult.Reset() - - m.assertEqual(isFail, not expectedAssertResult) end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertFalse tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @it("AssertFalse") @params(false, true) @@ -65,96 +69,1005 @@ namespace tests @params(1, false) @params("test", false) function _(value, expectedAssertResult) - m.assertFalse(value) + isFail = m.currentResult.isFail + m.currentResult.Reset() + m.assertEqual(isFail, not expectedAssertResult) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertEqual tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes with equal strings") + function _() + m.assertEqual("test", "test") + end function + + @it("passes with equal numbers") + function _() + m.assertEqual(1, 1) + m.assertEqual(1.0!, 1.0!) + m.assertEqual(1.0#, 1.0#) + m.assertEqual(1&, 1&) + end function + + @it("passes with equal booleans") + function _() + m.assertEqual(true, true) + m.assertEqual(false, false) + end function + + @it("passes with equal associative arrays") + function _() + m.assertEqual({ "test": 1 }, { "test": 1 }) + end function + + @it("passes with equal arrays") + function _() + m.assertEqual(["test", 1], ["test", 1]) + end function + + @it("passes with equal Nodes") + function _() + node = createObject("roSGNode", "Node") + m.assertEqual(node, node) + end function + + @it("passes with complex objects") + function _() + node = createObject("roSGNode", "Node") + func = function() + return 1 + end function + obj = { + "mockMethod": func + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + node: node + } + + obj2 = { + "mockMethod": func + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + node: node + } + + m.assertEqual(obj, obj2) + end function + + @it("passes with invalid") + function _() + m.assertEqual(invalid, invalid) + end function + + @it("passes with uninitialized") + function _() + m.assertEqual(uninitialized, uninitialized) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertLike tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes with equal strings") + function _() + m.assertLike("test", "test") + end function + + @it("passes with equal numbers") + function _() + m.assertLike(1, 1) + m.assertLike(1.0!, 1.0!) + m.assertLike(1.0!, 1.0#) + m.assertLike(1.0#, 1.0#) + m.assertLike(1&, 1&) + + end function + + @it("passes with equal booleans") + function _() + m.assertLike(true, true) + m.assertLike(false, false) + end function + + @it("passes with equal associative arrays") + function _() + m.assertLike({ "test": 1 }, { "test": 1 }) + end function + + @it("passes with equal arrays") + function _() + m.assertLike(["test", 1], ["test", 1]) + end function + + @it("passes with equal Nodes") + function _() + node = createObject("roSGNode", "Node") + m.assertLike(node, node) + end function + + @it("passes with complex objects") + function _() + node = createObject("roSGNode", "Node") + func = function() + return 1 + end function + obj = { + "mockMethod": func + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + node: node + } + + obj2 = { + "mockMethod": func + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + node: node + } + + m.assertLike(obj, obj2) + end function + + @it("passes with invalid") + function _() + m.assertLike(invalid, invalid) + end function + + @it("passes with uninitialized") + function _() + m.assertLike(uninitialized, uninitialized) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNotEqual tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes assertNotEqual on object vs uninitialized") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = uninitialized + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on uninitialized vs object") + function _() + obj = uninitialized + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object vs invalid") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = invalid + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on invalid vs object") + function _() + obj = invalid + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object vs array") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = [1, 2, 4, 3] + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on array vs object") + function _() + obj = [1, 2, 3] + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object1 vs object1") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object2 vs object1") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object vs string") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = "myString" + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on string vs object") + function _() + obj = "myString" + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object vs integer") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = 10 + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs object") + function _() + obj = 10 + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on object vs boolean") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = true + m.assertNotEqual(obj, obj2) + end function + @it("passes assertNotEqual on boolean vs object") + function _() + obj = true + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on uninitialized vs node") + function _() + obj = uninitialized + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs uninitialized") + function _() + obj = createObject("roSgNode", "Node") + obj2 = uninitialized + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs invalid") + function _() + obj = createObject("roSgNode", "Node") + obj2 = invalid + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on invalid vs node") + function _() + obj = invalid + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs node") + function _() + obj = createObject("roSgNode", "Node") + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs boolean") + function _() + obj = createObject("roSgNode", "Node") + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs node") + function _() + obj = true + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs string") + function _() + obj = createObject("roSgNode", "Node") + obj2 = "Node" + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on string vs node") + function _() + obj = "Node" + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs integer") + function _() + obj = createObject("roSgNode", "Node") + obj2 = 1 + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs node") + function _() + obj = 1 + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs array") + function _() + obj = createObject("roSgNode", "Node") + obj2 = [1, 2, 4, 3] + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on array vs node") + function _() + obj = [1, 2, 4, 3] + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on node vs AA") + function _() + obj = createObject("roSgNode", "Node") + obj2 = { "key": "value" } + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on AA vs node") + function _() + obj = { "key": "value" } + obj2 = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs uninitialized") + function _() + obj = true + obj2 = uninitialized + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on uninitialized vs boolean") + function _() + obj = uninitialized + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs invalid") + function _() + obj = true + obj2 = invalid + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on invalid vs boolean") + function _() + obj = invalid + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs array") + function _() + obj = true + obj2 = [1, 2, 4, 3] + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on array vs boolean") + function _() + obj = [1, 2, 4, 3] + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs string") + function _() + obj = true + obj2 = "true" + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on string vs boolean") + function _() + obj = "true" + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on boolean vs integer") + function _() + obj = true + obj2 = 1 + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs boolean") + function _() + obj = 1 + obj2 = true + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs uninitialized") + function _() + obj = 1 + obj2 = uninitialized + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on uninitialized vs integer") + function _() + obj = uninitialized + obj2 = 1 + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs invalid") + function _() + obj = 1 + obj2 = invalid + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on invalid vs integer") + function _() + obj = invalid + obj2 = 1 + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on integer vs string") + function _() + obj = 1 + obj2 = "1" + m.assertNotEqual(obj, obj2) + end function + + @it("passes assertNotEqual on string vs integer") + function _() + obj = "1" + obj2 = 1 + m.assertNotEqual(obj, obj2) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertInvalid tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes literal invalid") + function _() + m.assertInvalid(invalid) + end function + + @it("passes roInvalid object equivalent for intrinsic type 'Invalid'") + function _() + m.assertInvalid(createObject("roInvalid")) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNotInvalid tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes when provided a string") + function _() + m.assertNotInvalid("test") + end function + + @it("passes when provided a number") + function _() + m.assertNotInvalid(1) + m.assertNotInvalid(1.0!) + m.assertNotInvalid(1.0#) + m.assertNotInvalid(1&) + end function + + @it("passes when provided a boolean") + function _() + m.assertNotInvalid(true) + m.assertNotInvalid(false) + end function + + @it("passes when provided an associative array") + function _() + m.assertNotInvalid({}) + m.assertNotInvalid({ "test": 1 }) + end function + + @it("passes when provided an array") + function _() + m.assertNotInvalid([]) + m.assertNotInvalid(["test", 1]) + end function + + @it("passes when provided a node") + function _() + node = createObject("roSGNode", "Node") + m.assertNotInvalid(node) + end function + + @it("passes when provided a function") + function _() + func = function() + return 1 + end function + m.assertNotInvalid(func) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertAAHasKey tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes when aa has requested key") + function _() + aa = { "test": 1 } + m.assertAAHasKey(aa, "test") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertAANotHasKey tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes when aa does not have requested key") + function _() + aa = { "test": 1 } + m.assertAANotHasKey(aa, "missing") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertAAHasKeys tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes when aa has requested keys") + function _() + aa = { "one": 1, "two": 2, "three": 3 } + m.assertAAHasKeys(aa, ["one", "two"]) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertAANotHasKeys tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes when aa does not have requested keys") + function _() + aa = { "one": 1, "two": 2, "three": 3 } + m.assertAANotHasKeys(aa, ["four", "five"]) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayContains tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes assertArrayContains when value is in array with boolean search") + function _() + m.assertArrayContains([true], true) + end function + + @it("passes assertArrayContains when value is in array with integer search") + function _() + m.assertArrayContains([1], 1) + end function + + @it("passes assertArrayContains when value is in array with AA search") + function _() + m.assertArrayContains([{ one: 1, two: 2, three: 3 }], { one: 1, two: 2, three: 3 }) + end function + + @it("passes assertArrayContains when value is in array with node search") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayContains([node], node) + end function + + @it("passes assertArrayContains when value is in AA") + function _() + m.assertArrayContains({ one: 1, two: 2, three: 3 }, { one: 1, two: 2, three: 3 }) + end function + + @it("passes assertArrayContains when value is in AA with boolean search") + function _() + m.assertArrayContains({ one: true }, { one: true }) + end function + + @it("passes assertArrayContains when value is in AA with integer search") + function _() + m.assertArrayContains({ one: 1 }, { one: 1 }) + end function + + @it("passes assertArrayContains when value is in AA with array search") + function _() + m.assertArrayContains({ one: [1] }, { one: [1] }) + end function + + @it("passes assertArrayContains when value is in AA with node search") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayContains({ one: node }, { one: node }) + end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayContainsAAs tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("Fail") + @params([{ "one": 1 }], [{ "one": 2 }]) + @params([{ "one": 1 }], [{ "one": "a" }]) + @params([{ "one": 1 }], [{}]) + @params([{ "one": 1 }], [invalid]) + @params([{ "one": 1 }], []) + @params([{ "one": 1 }], invalid) + @params([{ "one": 1 }], [[]]) + @params([{ "one": 1 }], ["wrong"]) + @params([{ "one": 1 }], [2]) + @params([{ "one": "a" }], [{ "one": 1 }]) + @params([{ "two": 1 }], [{ "one": 1 }]) + @params([invalid], [{ "one": 1 }]) + @params(invalid, [{ "one": 1 }]) + @params([{ "one": 1, "two": 2 }], [{ "one": "1" }]) + @params([{ "one": 1 }, { "two": 2 }], [{ "one": "1" }, { "two": "a" }]) + @params([{ "one": 1 }, { "two": 2 }], [{ "a": 1 }, { "a": 1 }, { "a": 1 }]) + function _(expectedAAs, items) + m.assertArrayContainsAAs(items, expectedAAs) + isFail = m.currentResult.isFail + m.currentResult.Reset() + m.assertTrue(isFail) + end function + + @it("pass") + @params([], []) + @params([{}], [{}]) + @params([{ "one": 1 }], [{ "one": 1 }]) + @params([{ "one": 1, "two": 2 }], [{ "one": 1, "two": 2 }]) + @params([{ "one": 1, "two": 2 }], [{ "two": 2, "one": 1 }]) + @params([{ "one": 1, "two": 2 }, { "one": 1 }], [{ "one": 1 }, { "two": 2, "one": 1 }]) + @params([{ "one": 1, "two": 2 }, { "one": 1 }, { "three": 3 }], [{ "one": 1 }, { "three": 3 }, { "two": 2, "one": 1 }]) + function _(expectedAAs, items) + m.assertArrayContainsAAs(items, expectedAAs) isFail = m.currentResult.isFail m.currentResult.Reset() + m.assertFalse(isFail) + end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayNotContains tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.assertEqual(isFail, not expectedAssertResult) + @it("passes assertArrayNotContains when value is not in array") + function _() + m.assertArrayNotContains(["one"], invalid) + end function + + @it("passes assertArrayNotContains when value is not in AA") + function _() + m.assertArrayNotContains({ one: 1, two: 2, three: 3 }, invalid) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayContainsSubset tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes assertArrayContainsSubset with actual as array and subset found string") + function _() + m.assertArrayContainsSubset(["one", "two", "three"], ["three"]) + end function + + @it("passes assertArrayContainsSubset with actual as array and subset found boolean") + function _() + m.assertArrayContainsSubset([true, false], [true]) + end function + + @it("passes assertArrayContainsSubset with actual as array and subset found integer") + function _() + m.assertArrayContainsSubset([1, 2, 3], [3]) + end function + + @it("passes assertArrayContainsSubset with actual as array and subset found node") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayContainsSubset([node], [node]) + end function + + @it("passes assertArrayContainsSubset with actual as AA and subset found string") + function _() + m.assertArrayContainsSubset({ one: "1", two: "2", three: "3" }, { three: "3" }) + end function + + @it("passes assertArrayContainsSubset with actual as AA and subset found boolean") + function _() + m.assertArrayContainsSubset({ one: true, two: false }, { two: false }) + end function + + @it("passes assertArrayContainsSubset with actual as AA and subset found integer") + function _() + m.assertArrayContainsSubset({ one: 1, two: 2, three: 3 }, { three: 3 }) + end function + + @it("passes assertArrayContainsSubset with actual as AA and subset found node") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayContainsSubset({ one: node }, { one: node }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayNotContainsSubset tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes assertArrayNotContainsSubset with actual as array and subset not found") + function _() + m.assertArrayNotContainsSubset(["one", "two", "three"], ["four"]) + end function + + @it("passes assertArrayNotContainsSubset with actual as AA and subset not found") + function _() + m.assertArrayNotContainsSubset({ one: 1, two: 2, three: 3 }, { four: 3 }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertArrayCount tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("passes assertArrayCount on matching count on array") + function _() + m.assertArrayCount([1, 2], 2) + end function + + @it("passes assertArrayCount on matching count on AA") + function _() + m.assertArrayCount({ one: 1, two: 2 }, 2) end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("tests AssertArrayContainsAAs") + @describe("AssertArrayNotCount tests") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @it("Fail") - @params([{ "one": 1 }], [{ "one": 2 }]) - @params([{ "one": 1 }], [{ "one": "a" }]) - @params([{ "one": 1 }], [{}]) - @params([{ "one": 1 }], [invalid]) - @params([{ "one": 1 }], []) - @params([{ "one": 1 }], invalid) - @params([{ "one": 1 }], [[]]) - @params([{ "one": 1 }], ["wrong"]) - @params([{ "one": 1 }], [2]) - @params([{ "one": "a" }], [{ "one": 1 }]) - @params([{ "two": 1 }], [{ "one": 1 }]) - @params([invalid], [{ "one": 1 }]) - @params(invalid, [{ "one": 1 }]) - @params([{ "one": 1, "two": 2 }], [{ "one": "1" }]) - @params([{ "one": 1 }, { "two": 2 }], [{ "one": "1" }, { "two": "a" }]) - @params([{ "one": 1 }, { "two": 2 }], [{ "a": 1 }, { "a": 1 }, { "a": 1 }]) - function _(expectedAAs, items) + @it("passes AssertArrayNotCount miss match count on array") + function _() + m.assertArrayNotCount([1, 2], 1) + end function - m.assertArrayContainsAAs(items, expectedAAs) + @it("passes AssertArrayNotCount miss match count on AA") + function _() + m.assertArrayNotCount({ one: 1, two: 2 }, 1) + end function - isFail = m.currentResult.isFail - m.currentResult.Reset() + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertEmpty tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("passes assertEmpty on empty array") + function _() + m.assertEmpty([]) + end function - m.assertTrue(isFail) + @it("passes assertEmpty on empty aa") + function _() + m.assertEmpty({}) end function + @it("passes assertEmpty on empty string") + function _() + m.assertEmpty("") + end function @it("pass") - @params([], []) - @params([{}], [{}]) - @params([{ "one": 1 }], [{ "one": 1 }]) - @params([{ "one": 1, "two": 2 }], [{ "one": 1, "two": 2 }]) - @params([{ "one": 1, "two": 2 }], [{ "two": 2, "one": 1 }]) - @params([{ "one": 1, "two": 2 }, { "one": 1 }], [{ "one": 1 }, { "two": 2, "one": 1 }]) - @params([{ "one": 1, "two": 2 }, { "one": 1 }, { "three": 3 }], [{ "one": 1 }, { "three": 3 }, { "two": 2, "one": 1 }]) - function _(expectedAAs, items) - - m.assertArrayContainsAAs(items, expectedAAs) - + @params([]) + @params({}) + @params("") + function _(values) + m.assertEmpty(values) isFail = m.currentResult.isFail - m.currentResult.Reset() - m.assertFalse(isFail) end function + @it("fail") + @params(1) + @params(invalid) + @params(["one", "two", "three"]) + @params([1, 2, 3]) + @params([true]) + @params([[true, true], [false, false]]) + @params([{ "test": 1 }]) + @params("not empty") + @params([invalid]) + function _(values) + m.assertEmpty(values) + isFail = m.currentResult.isFail + m.currentResult.Reset() + m.assertTrue(isFail) + end function + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("tests global is present on testSuite") + @describe("AssertNotEmpty tests") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @beforeEach - function test_assertGlobal_beforeEach() - m.beforeEachGlobal = m.global + @it("passes assertEmpty on non-empty array") + function _() + m.assertNotEmpty([1]) end function - @afterEach - function test_assertGlobal_afterEach() - m.afterEachGlobal = m.global + @it("passes assertEmpty on non-empty aa") + function _() + m.assertNotEmpty({ one: 1 }) end function - @it("global is in test") + @it("passes assertEmpty on non-empty string") function _() - m.assertNotInvalid(m.global) + m.assertNotEmpty("2") end function - @it("global is in before each and after each") - function _() - m.assertNotInvalid(m.global) - m.assertNotInvalid(m.beforeEachGlobal) - m.assertNotInvalid(m.afterEachGlobal) + @it("pass") + @params(["one", "two", "three"]) + @params([1, 2, 3]) + @params([true]) + @params([[true, true], [false, false]]) + @params([{ "test": 1 }]) + @params("not empty") + @params([invalid]) + function _(values) + m.assertNotEmpty(values) + isFail = m.currentResult.isFail + m.currentResult.Reset() + m.assertFalse(isFail) + end function + + @it("fail") + @params(invalid) + @params([]) + @params({}) + @params(1) + @params("") + function _(values) + m.assertNotEmpty(values) + isFail = m.currentResult.isFail + m.currentResult.Reset() + m.assertTrue(isFail) end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("tests AssertArrayContainsOnlyValuesOfType") + @describe("AssertArrayContainsOnlyValuesOfType tests ") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @it("pass") @@ -164,14 +1077,10 @@ namespace tests @params([[true, true], [false, false]], "Array") @params([{ "test": 1 }, { "test": 1 }], "AssociativeArray") function _(values, typeName) - m.assertArrayContainsOnlyValuesOfType(values, typeName) isFail = m.currentResult.isFail - m.currentResult.Reset() - m.assertFalse(isFail) - end function @it("fail") @@ -187,95 +1096,206 @@ namespace tests @params([[true, true], [false, false]], "AssociativeArray") @params([{ "test": 1 }, { "test": 1 }], "Array") function _(values, typeName) - m.assertArrayContainsOnlyValuesOfType(values, typeName) isFail = m.currentResult.isFail m.currentResult.Reset() + m.assertTrue(isFail) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertType tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("passes assertType on type match string") + function _() + m.assertType("", "String") + end function - m.assertTrue(isFail) + @it("passes assertType on type match boolean") + function _() + m.assertType(true, "Boolean") + m.assertType(false, "Boolean") + end function + + @it("passes assertType on type match integer") + function _() + m.assertType(1, "Integer") + end function + + @it("passes assertType on type match AA") + function _() + m.assertType({}, "roAssociativeArray") + end function + @it("passes assertType on type match array") + function _() + m.assertType([], "roArray") + end function + @it("passes assertType on type match node") + function _() + m.assertType(createObject("roSGNode", "Node"), "roSGNode") end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("tests AssertNotEmpty") + @describe("AssertSubType tests") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @it("pass") - @params(["one", "two", "three"]) - @params([1, 2, 3]) - @params([true]) - @params([[true, true], [false, false]]) - @params([{ "test": 1 }]) - @params("not empty") - @params([invalid]) - function _(values) + @it("passes assertType on type match subtypes") + function _() + m.assertSubType(createObject("roSgNode", "Node"), "Node") + m.assertSubType(createObject("roSgNode", "NodeExample"), "NodeExample") + end function - m.assertNotEmpty(values) - isFail = m.currentResult.isFail + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertClass tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.currentResult.Reset() + @it("passes AssertClass on class name match") + @params({ __classname: "myClass" }) + function _(value) + m.assertClass(value, "myClass") + end function - m.assertFalse(isFail) + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeCount tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("passes assertNodeCount on child count match") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeCount(node, 10) end function - @it("fail") - @params(invalid) - @params([]) - @params({}) - @params(1) - @params("") - function _(values) + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeNotCount tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.assertNotEmpty(values) - isFail = m.currentResult.isFail + @it("passes assertNodeNotCount on child count miss match") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotCount(node, 5) + end function - m.currentResult.Reset() + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeEmpty tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.assertTrue(isFail) + @it("passes assertNodeEmpty on empty node") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeEmpty(node) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeNotEmpty tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("passes assertNodeNotEmpty on non-empty node") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotEmpty(node) end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @describe("tests AssertEmpty") + @describe("AssertNodeContains tests") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @it("pass") - @params([]) - @params({}) - @params("") - function _(values) + @it("passes assertNodeContains when child is found") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeContains(node, node.getChild(5)) + end function - m.assertEmpty(values) - isFail = m.currentResult.isFail + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeContainsOnly tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.currentResult.Reset() + @it("passes assertNodeContainsOnly when child is found and contains not other children") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(1, "Group") + m.assertNodeContainsOnly(node, node.getChild(0)) + end function - m.assertFalse(isFail) + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeNotContains tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @it("passes assertNodeNotContains when child is not found") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotContains(node, createObject("roSGNode", "Node")) end function - @it("fail") - @params(1) - @params(invalid) - @params(["one", "two", "three"]) - @params([1, 2, 3]) - @params([true]) - @params([[true, true], [false, false]]) - @params([{ "test": 1 }]) - @params("not empty") - @params([invalid]) - function _(values) + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeContainsFields tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.assertEmpty(values) - isFail = m.currentResult.isFail + @it("passes assertNodeContainsFields fields are found") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeContainsFields(node, { "id": "", "focusable": false }) + end function - m.currentResult.Reset() + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertNodeNotContainsFields tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - m.assertTrue(isFail) + @it("passes assertNodeNotContainsFields legacy find child support in array subset") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotContainsFields(node, [createObject("roSGNode", "Node"), createObject("roSGNode", "Node")]) + end function + + @it("passes assertNodeNotContainsFields subset AA of fields does not match on node") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeNotContainsFields(node, { "focusable": true }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("AssertAAContainsSubset tests") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + @it("passes assertAAContainsSubset with matching subset of values") + function _() + node = createObject("roSGNode", "Node") + m.assertAAContainsSubset({ one: 1, two: 2, three: 3 }, { one: 1, two: 2 }) + end function + + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests global is present on testSuite") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @beforeEach + function test_assertGlobal_beforeEach() + m.beforeEachGlobal = m.global + end function + @afterEach + function test_assertGlobal_afterEach() + m.afterEachGlobal = m.global + end function + + @it("global is in test") + function _() + m.assertNotInvalid(m.global) + end function + + @it("global is in before each and after each") + function _() + m.assertNotInvalid(m.global) + m.assertNotInvalid(m.beforeEachGlobal) + m.assertNotInvalid(m.afterEachGlobal) end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -589,40 +1609,11 @@ namespace tests end function - 'ASSERTIONS TO WRITE TESTS FOR! + 'ASSERTIONS TO WRITE OR CLARIFY TESTS FOR! 'This is coming soon! - ' AssertEqual ' AssertLike - ' AssertNotEqual - ' AssertInvalid - ' AssertNotInvalid - ' AssertAAHasKey - ' AssertAANotHasKey - ' AssertAAHasKeys - ' AssertAANotHasKeys - ' AssertArrayNotContains - ' AssertArrayContainsSubset - ' AssertArrayNotContainsSubsetet - ' AssertArrayCount - ' AssertArrayNotCount - ' AssertArrayContainsOnly - ' AssertType - ' AssertSubType - ' - ' 'Node extensions - ' AssertNodeCount - ' AssertNodeNotCount - ' AssertNodeEmpty - ' AssertNodeNotEmpty - ' AssertNodeContains - ' AssertNodeNotContains - ' AssertNodeContainsFields - ' AssertNodeNotContainsFields - - ' AssertArray - ' AssertAAContainsSubset ' ' 'Mocking and stubbing ' AssertMocks diff --git a/tests/src/source/Async.spec.bs b/tests/src/source/Async.spec.bs index 49e78337..c77bcc65 100644 --- a/tests/src/source/Async.spec.bs +++ b/tests/src/source/Async.spec.bs @@ -20,7 +20,6 @@ namespace tests m.wasCalled = true m.testSuite.assertTrue(true) m.testSuite.done() - ? "done one" end sub delayCall(0.02, callbackFunction) @@ -34,7 +33,6 @@ namespace tests m.wasCalled = true m.testSuite.assertTrue(true) m.testSuite.done() - ? "done two" end function) delayCall(0.02, globalFunctionWithoutReturn) @@ -48,7 +46,6 @@ namespace tests m.testSuite.assertEqual(text, "") m.testSuite.assertTrue(true) m.testSuite.done() - ? "done three" end function) delayCall(0.02, SetLabelText) diff --git a/tests/src/source/Basic.spec.bs b/tests/src/source/Basic.spec.bs index 04af20b6..140f858b 100644 --- a/tests/src/source/Basic.spec.bs +++ b/tests/src/source/Basic.spec.bs @@ -54,12 +54,12 @@ namespace tests @describe("assertAsyncField") '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - @ignore("takes ages") @it("times out") + @slow(150) function _() item = { "id": "item" } - m.assertAsyncField(item, "id") + m.assertAsyncField(item, "id", 10) isFail = m.currentResult.isFail m.currentResult.Reset() m.assertTrue(isFail) @@ -190,7 +190,7 @@ namespace tests m.currentResult.Reset() m.assertTrue(isFail) - m.assertEqual(msg, `["one", "two", "three"] != ["2one", "2two", "2three"]`) + m.assertEqual(msg, `expected "[ "one", "two", "three" ]" to equal "[ "2one", "2two", "2three" ]"`) end function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/tests/src/source/FailedAssertion.spec.bs b/tests/src/source/FailedAssertion.spec.bs new file mode 100644 index 00000000..14b6d481 --- /dev/null +++ b/tests/src/source/FailedAssertion.spec.bs @@ -0,0 +1,1649 @@ +import "pkg:/source/rooibos/BaseTestSuite.bs" + +namespace tests + + @noEarlyExit + @suite("Rooibos failed assertion tests") + class FailedAssertionTests extends rooibos.BaseTestSuite + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests fail on crash") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("reports error") + function _() + throw "some error" + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertTrue fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("AssertTrue with message") + @params(false) + @params(invalid) + @params(0) + @params(1) + @params("test") + @params({}) + @params([]) + function _(value) + m.assertTrue(value) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertFalse fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("AssertFalse") + @params(true) + @params(invalid) + @params(0) + @params(1) + @params("test") + @params({}) + @params([]) + function _(value) + m.assertFalse(value) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertEqual fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("assertEqual fail on object vs uninitialized") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = uninitialized + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on uninitialized vs object") + function _() + obj = uninitialized + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object vs invalid") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = invalid + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on invalid vs object") + function _() + obj = invalid + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object vs array") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = [1, 2, 4, 3] + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on array vs object") + function _() + obj = [1, 2, 3] + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object1 vs object1") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object2 vs object1") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object vs string") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = "myString" + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on string vs object") + function _() + obj = "myString" + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object vs integer") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = 10 + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs object") + function _() + obj = 10 + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on object vs boolean") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs object") + function _() + obj = true + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on uninitialized vs node") + function _() + obj = uninitialized + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs uninitialized") + function _() + obj = createObject("roSgNode", "Node") + obj2 = uninitialized + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs invalid") + function _() + obj = createObject("roSgNode", "Node") + obj2 = invalid + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on invalid vs node") + function _() + obj = invalid + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs node") + function _() + obj = createObject("roSgNode", "Node") + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs boolean") + function _() + obj = createObject("roSgNode", "Node") + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs node") + function _() + obj = true + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs string") + function _() + obj = createObject("roSgNode", "Node") + obj2 = "Node" + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on string vs node") + function _() + obj = "Node" + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs integer") + function _() + obj = createObject("roSgNode", "Node") + obj2 = 1 + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs node") + function _() + obj = 1 + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs array") + function _() + obj = createObject("roSgNode", "Node") + obj2 = [1, 2, 4, 3] + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on array vs node") + function _() + obj = [1, 2, 4, 3] + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on node vs AA") + function _() + obj = createObject("roSgNode", "Node") + obj2 = { "key": "value" } + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on AA vs node") + function _() + obj = { "key": "value" } + obj2 = createObject("roSgNode", "Node") + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs uninitialized") + function _() + obj = true + obj2 = uninitialized + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on uninitialized vs boolean") + function _() + obj = uninitialized + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs invalid") + function _() + obj = true + obj2 = invalid + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on invalid vs boolean") + function _() + obj = invalid + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs array") + function _() + obj = true + obj2 = [1, 2, 4, 3] + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on array vs boolean") + function _() + obj = [1, 2, 4, 3] + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs string") + function _() + obj = true + obj2 = "true" + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on string vs boolean") + function _() + obj = "true" + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on boolean vs integer") + function _() + obj = true + obj2 = 1 + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs boolean") + function _() + obj = 1 + obj2 = true + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs uninitialized") + function _() + obj = 1 + obj2 = uninitialized + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on uninitialized vs integer") + function _() + obj = uninitialized + obj2 = 1 + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs invalid") + function _() + obj = 1 + obj2 = invalid + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on invalid vs integer") + function _() + obj = invalid + obj2 = 1 + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on integer vs string") + function _() + obj = 1 + obj2 = "1" + m.assertEqual(obj, obj2) + end function + + @it("assertEqual fail on string vs integer") + function _() + obj = "1" + obj2 = 1 + m.assertEqual(obj, obj2) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertLike fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("assertLike fail on object vs uninitialized") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = uninitialized + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on uninitialized vs object") + function _() + obj = uninitialized + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object vs invalid") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = invalid + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on invalid vs object") + function _() + obj = invalid + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object vs array") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = [1, 2, 4, 3] + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on array vs object") + function _() + obj = [1, 2, 3] + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object1 vs object1") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object2 vs object1") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object vs string") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = "myString" + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on string vs object") + function _() + obj = "myString" + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object vs integer") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = 10 + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on integer vs object") + function _() + obj = 10 + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on object vs boolean") + function _() + obj = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs object") + function _() + obj = true + obj2 = { + "mockMethod": invalid + key: "value1" + object: { + "otherKey1": "value" + } + array: [1, 2, 4, 3] + } + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs uninitialized") + function _() + obj = true + obj2 = uninitialized + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on uninitialized vs boolean") + function _() + obj = uninitialized + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs invalid") + function _() + obj = true + obj2 = invalid + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on invalid vs boolean") + function _() + obj = invalid + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs array") + function _() + obj = true + obj2 = [1, 2, 4, 3] + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on array vs boolean") + function _() + obj = [1, 2, 4, 3] + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs string") + function _() + obj = true + obj2 = "true" + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on string vs boolean") + function _() + obj = "true" + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on boolean vs integer") + function _() + obj = true + obj2 = 1 + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on integer vs boolean") + function _() + obj = 1 + obj2 = true + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on integer vs uninitialized") + function _() + obj = 1 + obj2 = uninitialized + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on uninitialized vs integer") + function _() + obj = uninitialized + obj2 = 1 + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on integer vs invalid") + function _() + obj = 1 + obj2 = invalid + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on invalid vs integer") + function _() + obj = invalid + obj2 = 1 + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on integer vs string") + function _() + obj = 1 + obj2 = "1" + m.assertLike(obj, obj2) + end function + + @it("assertLike fail on string vs integer") + function _() + obj = "1" + obj2 = 1 + m.assertLike(obj, obj2) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNotEqual fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNotEqual on node vs node") + function _() + obj = createObject("roSgNode", "Node") + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on object vs object") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on array vs array") + function _() + obj = [1, 2, 3] + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on string vs string") + function _() + obj = "myString" + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on integer vs integer") + function _() + obj = 10 + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on boolean vs boolean") + function _() + obj = true + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on uninitialized vs uninitialized") + function _() + obj = uninitialized + m.assertNotEqual(obj, obj) + end function + + @it("fail assertNotEqual on invalid vs invalid") + function _() + obj = invalid + m.assertNotEqual(obj, obj) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertInvalid fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertInvalid on node") + function _() + obj = createObject("roSgNode", "Node") + m.assertInvalid(obj) + end function + + @it("fail assertInvalid on object") + function _() + obj = { + "mockMethod": function() + return 1 + end function + key: "value" + object: { + "otherKey": "value" + } + array: [1, 2, 3] + } + m.assertInvalid(obj) + end function + + @it("fail assertInvalid on array") + function _() + m.assertInvalid([1, 2, 3]) + end function + + @it("fail assertInvalid on string") + function _() + m.assertInvalid("myString") + end function + + @it("fail assertInvalid on integer") + function _() + m.assertInvalid(10) + end function + + @it("fail assertInvalid on boolean") + function _() + m.assertInvalid(true) + end function + + @it("fail assertInvalid on uninitialized") + function _() + m.assertInvalid(uninitialized) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNotInvalid fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNotInvalid on invalid") + function _() + m.assertNotInvalid(invalid) + end function + + @it("failed assertInvalid on roInvalid object equivalent for intrinsic type 'Invalid'") + function _() + m.assertNotInvalid(createObject("roInvalid")) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertAAHasKey fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertAAHasKey on invalid") + function _() + m.assertAAHasKey(invalid, "one") + end function + + @it("fail assertAAHasKey on missing key") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertAAHasKey(obj, "four") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertAAHasKeys fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertAAHasKeys with missing actual AA") + function _() + m.assertAAHasKeys(invalid, ["one"]) + end function + + @it("fail assertAAHasKeys when keys are found") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertAAHasKeys(obj, ["one", "two", "three", "four", "five", "six'", "seven"""]) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertAANotHasKey fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertAANotHasKey on invalid") + function _() + m.assertAANotHasKey(invalid, "one") + end function + + @it("fail assertAANotHasKey on found key") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertAANotHasKey(obj, "one") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayContains fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail AssertArrayContains with missing actual array") + function _() + m.AssertArrayContains(invalid, "one") + end function + + @it("fail AssertArrayContains when value is not in array") + function _() + m.AssertArrayContains(["one"], invalid) + end function + + @it("fail AssertArrayContains when value is not in AA") + function _() + m.AssertArrayContains({ one: 1, two: 2, three: 3 }, invalid) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayContainsAAs fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayContainsAAs with missing actual array") + function _() + m.assertArrayContainsAAs(invalid, [{ "one": 1, "two": 2, "three": 3, "four": 4 }]) + end function + + @it("fail assertArrayContainsAAs with missing search array") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertArrayContainsAAs([obj], invalid) + end function + + @it("fail assertArrayContainsAAs with invalid value in search array") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertArrayContainsAAs([obj], [invalid]) + end function + + @it("fail assertArrayContainsAAs did not contain aa") + function _() + obj = { + one: 1 + two: 2 + three: 3 + } + m.assertArrayContainsAAs([obj], [{ "one": 1, "two": 2, "three": 3, "four": 4 }]) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayNotContains fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayNotContains with missing actual array") + function _() + m.assertArrayNotContains(invalid, "one") + end function + + @it("fail assertArrayNotContains when value is in array") + function _() + m.assertArrayNotContains(["one"], "one") + end function + + @it("fail assertArrayNotContains when value is in array with boolean search") + function _() + m.assertArrayNotContains([true], true) + end function + + @it("fail assertArrayNotContains when value is in array with integer search") + function _() + m.assertArrayNotContains([1], 1) + end function + + @it("fail assertArrayNotContains when value is in array with AA search") + function _() + m.assertArrayNotContains([{ one: 1, two: 2, three: 3 }], { one: 1, two: 2, three: 3 }) + end function + + @it("fail assertArrayNotContains when value is in array with node search") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayNotContains([node], node) + end function + + @it("fail assertArrayNotContains when value is in AA") + function _() + m.assertArrayNotContains({ one: 1, two: 2, three: 3 }, { one: 1, two: 2, three: 3 }) + end function + + @it("fail assertArrayNotContains when value is in AA with boolean search") + function _() + m.assertArrayNotContains({ one: true }, { one: true }) + end function + + @it("fail assertArrayNotContains when value is in AA with integer search") + function _() + m.assertArrayNotContains({ one: 1 }, { one: 1 }) + end function + + @it("fail assertArrayNotContains when value is in AA with array search") + function _() + m.assertArrayNotContains({ one: [1] }, { one: [1] }) + end function + + @it("fail assertArrayNotContains when value is in AA with node search") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayNotContains({ one: node }, { one: node }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayContainsSubset fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayContainsSubset with missing actual array") + function _() + m.assertArrayContainsSubset(invalid, []) + end function + + @it("fail assertArrayContainsSubset with missing actual subset") + function _() + m.assertArrayContainsSubset([], invalid) + end function + + @it("fail assertArrayContainsSubset with actual as array and subset as AA") + function _() + m.assertArrayContainsSubset([], {}) + end function + + @it("fail assertArrayContainsSubset with actual as AA and subset as array") + function _() + m.assertArrayContainsSubset({}, []) + end function + + @it("fail assertArrayContainsSubset with actual as array and subset not found") + function _() + m.assertArrayContainsSubset(["one", "two", "three"], ["four"]) + end function + + @it("fail assertArrayContainsSubset with actual as AA and subset not found") + function _() + m.assertArrayContainsSubset({ one: 1, two: 2, three: 3 }, { four: 3 }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayNotContainsSubset fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayNotContainsSubset with missing actual array") + function _() + m.assertArrayNotContainsSubset(invalid, []) + end function + + @it("fail assertArrayNotContainsSubset with missing actual subset") + function _() + m.assertArrayNotContainsSubset([], invalid) + end function + + @it("fail assertArrayNotContainsSubset with actual as array and subset as AA") + function _() + m.assertArrayNotContainsSubset([], {}) + end function + + @it("fail assertArrayNotContainsSubset with actual as AA and subset as array") + function _() + m.assertArrayNotContainsSubset({}, []) + end function + + @it("fail assertArrayNotContainsSubset with actual as array and subset found string") + function _() + m.assertArrayNotContainsSubset(["one", "two", "three"], ["three"]) + end function + + @it("fail assertArrayNotContainsSubset with actual as array and subset found boolean") + function _() + m.assertArrayNotContainsSubset([true, false], [true]) + end function + + @it("fail assertArrayNotContainsSubset with actual as array and subset found integer") + function _() + m.assertArrayNotContainsSubset([1, 2, 3], [3]) + end function + + @it("fail assertArrayNotContainsSubset with actual as array and subset found node") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayNotContainsSubset([node], [node]) + end function + + + @it("fail assertArrayNotContainsSubset with actual as AA and subset found string") + function _() + m.assertArrayNotContainsSubset({ one: "1", two: "2", three: "3" }, { three: "3" }) + end function + + @it("fail assertArrayNotContainsSubset with actual as AA and subset found boolean") + function _() + m.assertArrayNotContainsSubset({ one: true, two: false }, { two: false }) + end function + + @it("fail assertArrayNotContainsSubset with actual as AA and subset found integer") + function _() + m.assertArrayNotContainsSubset({ one: 1, two: 2, three: 3 }, { three: 3 }) + end function + + @it("fail assertArrayNotContainsSubset with actual as AA and subset found node") + function _() + node = createObject("roSgNode", "Node") + m.assertArrayNotContainsSubset({ one: node }, { one: node }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayCount fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayCount missing array") + function _() + m.assertArrayCount(invalid, 1) + end function + + @it("fail assertArrayCount miss match count on array") + function _() + m.assertArrayCount([1, 2], 1) + end function + + @it("fail assertArrayCount miss match count on AA") + function _() + m.assertArrayCount({ one: 1, two: 2 }, 1) + end function + + @it("fail assertArrayCount bad count value") + function _() + m.assertArrayCount({ one: 1, two: 2 }, "1") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayNotCount fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayNotCount missing array") + function _() + m.assertArrayNotCount(invalid, 1) + end function + + @it("fail assertArrayNotCount miss match count on array") + function _() + m.assertArrayNotCount([1, 2], 2) + end function + + @it("fail assertArrayNotCount miss match count on AA") + function _() + m.assertArrayNotCount({ one: 1, two: 2 }, 2) + end function + + @it("fail assertArrayNotCount bad count value") + function _() + m.assertArrayNotCount({ one: 1, two: 2 }, "2") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertEmpty fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertEmpty bad input") + function _() + m.assertEmpty(invalid) + end function + + @it("fail assertEmpty non empty array") + function _() + m.assertEmpty([1]) + end function + + @it("fail assertEmpty non empty aa") + function _() + m.assertEmpty({ one: 1 }) + end function + + @it("fail assertEmpty non empty string") + function _() + m.assertEmpty("2") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNotEmpty fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNotEmpty bad input") + function _() + m.assertNotEmpty(invalid) + end function + + @it("fail assertNotEmpty empty array") + function _() + m.assertNotEmpty([]) + end function + + @it("fail assertNotEmpty empty aa") + function _() + m.assertNotEmpty({}) + end function + + @it("fail assertNotEmpty empty string") + function _() + m.assertNotEmpty("") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertArrayContainsOnlyValuesOfType fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertArrayContainsOnlyValuesOfType bad input type") + function _() + m.assertArrayContainsOnlyValuesOfType(invalid, "bad value") + end function + + @it("fail assertArrayContainsOnlyValuesOfType bad input value") + function _() + m.assertArrayContainsOnlyValuesOfType(invalid, "String") + end function + + @it("fail assertArrayContainsOnlyValuesOfType array of invalid values") + function _() + m.assertArrayContainsOnlyValuesOfType([invalid], "String") + end function + + @it("fail assertArrayContainsOnlyValuesOfType AA of invalid values") + function _() + m.assertArrayContainsOnlyValuesOfType({ key: invalid }, "String") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertType fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertType type mismatch string") + function _() + m.assertType(invalid, "string") + end function + + @it("fail assertType type mismatch boolean") + function _() + m.assertType(invalid, "boolean") + end function + + @it("fail assertType type mismatch integer") + function _() + m.assertType(invalid, "integer") + end function + + @it("fail assertType type mismatch AA") + function _() + m.assertType(invalid, "roAssociativeArray") + end function + + @it("fail assertType type mismatch array") + function _() + m.assertType(invalid, "roArray") + end function + + @it("fail assertType type mismatch node") + function _() + m.assertType(invalid, "roSGNode") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertSubType fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertType type mismatch non-node") + function _() + m.assertSubType(createObject("roDateTime"), "MyNode") + end function + + @it("fail assertType type mismatch primitive") + function _() + m.assertSubType(true, "MyNode") + end function + + @it("fail assertType type mismatch subtypes") + function _() + m.assertSubType(createObject("roSgNode", "Node"), "MyNode") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertClass fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fails if not passed a class") + @params(invalid) + @params([]) + @params("wrong") + function _(value) + m.assertClass(value, "myClass") + end function + + @it("fails if wrong class name") + @params({}) + @params({ __classname: invalid }) + @params({ __classname: "other" }) + function _(value) + m.assertClass(value, "myClass") + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeCount fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeCount missing node") + function _() + m.assertNodeCount(invalid, 0) + end function + + @it("fail assertNodeCount on count miss match") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeCount(node, 5) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeNotCount fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeNotCount missing node") + function _() + m.assertNodeNotCount(invalid, 0) + end function + + @it("fail assertNodeNotCount on count miss match") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotCount(node, 10) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeEmpty fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeEmpty missing node") + function _() + m.assertNodeEmpty(invalid) + end function + + @it("fail assertNodeEmpty on non-empty node") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeEmpty(node) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeNotEmpty fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeNotEmpty missing node") + function _() + m.assertNodeNotEmpty(invalid) + end function + + @it("fail assertNodeNotEmpty on empty node") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeNotEmpty(node) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeContains fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeContains missing node") + function _() + m.assertNodeContains(invalid, invalid) + end function + + @it("fail assertNodeContains when child not found") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeContains(node, createObject("roSGNode", "Group")) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeContainsOnly fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeContainsOnly missing node") + function _() + m.assertNodeContainsOnly(invalid, invalid) + end function + + @it("fail assertNodeContainsOnly when child if found but contains other children") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeContainsOnly(node, node.getChild(0)) + end function + + @it("fail assertNodeContainsOnly when child is not found") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(1, "Group") + m.assertNodeContainsOnly(node, createObject("roSGNode", "Group")) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeNotContains fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeNotContains missing node") + function _() + m.assertNodeNotContains(invalid, invalid) + end function + + @it("fail assertNodeNotContains when child is found") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotContains(node, node.getChild(5)) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeContainsFields fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeContainsFields missing node") + function _() + m.assertNodeContainsFields(invalid, invalid) + end function + + @it("fail assertNodeContainsFields bad subset value") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeContainsFields(node, []) + end function + + @it("fail assertNodeContainsFields fields are not found") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeContainsFields(node, { "field": "value", "id": "", "focusable": false, "": false }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertNodeNotContainsFields fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertNodeNotContainsFields with missing node") + function _() + m.assertNodeNotContainsFields(invalid, []) + end function + + @it("fail assertNodeNotContainsFields with missing subset") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeNotContainsFields(node, invalid) + end function + + @it("fail assertNodeNotContainsFields legacy find child support in array subset") + function _() + node = createObject("roSGNode", "Node") + node.createChildren(10, "Group") + m.assertNodeNotContainsFields(node, [node.getChild(5)]) + end function + + @it("fail assertNodeNotContainsFields subset AA of fields") + function _() + node = createObject("roSGNode", "Node") + m.assertNodeNotContainsFields(node, { "focusable": false }) + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("tests AssertAAContainsSubset fail") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("fail assertAAContainsSubset with missing actual AA") + function _() + m.assertAAContainsSubset(invalid, {}) + end function + + @it("fail assertAAContainsSubset with missing subset") + function _() + node = createObject("roSGNode", "Node") + m.assertAAContainsSubset({ one: 1, two: 2, three: 3 }, invalid) + end function + + @it("fail assertAAContainsSubset with subset item not found") + function _() + node = createObject("roSGNode", "Node") + m.assertAAContainsSubset({ one: 1, two: 2, three: 3 }, { three: 4 }) + end function + + 'ASSERTIONS TO WRITE TESTS FOR! + + 'This is coming soon! + + ' AssertEqual + ' AssertLike + ' AssertNotEqual + ' AssertInvalid + ' AssertNotInvalid + ' AssertAAHasKey + ' AssertAANotHasKey + ' AssertAAHasKeys + ' AssertAANotHasKeys + ' AssertArrayNotContains + ' AssertArrayContainsSubset + ' AssertArrayNotContainsSubsetet + ' AssertArrayCount + ' AssertArrayNotCount + ' AssertArrayContainsOnly + ' AssertType + ' AssertSubType + ' + ' 'Node extensions + ' AssertNodeCount + ' AssertNodeNotCount + ' AssertNodeEmpty + ' AssertNodeNotEmpty + ' AssertNodeContains + ' AssertNodeNotContains + ' AssertNodeContainsFields + ' AssertNodeNotContainsFields + + ' AssertArray + ' AssertAAContainsSubset + ' + ' 'Mocking and stubbing + ' AssertMocks + ' MockFail + end class +end namespace diff --git a/tests/src/source/NodeExample.spec.bs b/tests/src/source/NodeExample.spec.bs index f635ed08..2cfcb19b 100644 --- a/tests/src/source/NodeExample.spec.bs +++ b/tests/src/source/NodeExample.spec.bs @@ -57,7 +57,6 @@ namespace tests m.age = age callback = function() - ? "*** timer triggering anonymous callback function" m.testSuite.assertTrue(m.testSuite.age >= 18) m.testSuite.done() end function @@ -95,6 +94,5 @@ namespace tests end namespace function OnTimer() - ? " TIMER IS FIRED" m.testSuite.done() end function