diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8346eb20..7d542c8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-node@master with: - node-version: "14.18.1" + node-version: "16.20.2" # - run: npm run build - run: cd bsc-plugin && npm install - run: cd bsc-plugin && npm run preversion @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@master - uses: actions/setup-node@master with: - node-version: "10.19.0" + node-version: "16.20.2" - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ./.npmrc - run: npm ci - run: npm run build diff --git a/bsc-plugin/src/lib/rooibos/Annotation.ts b/bsc-plugin/src/lib/rooibos/Annotation.ts index 23c9d831..dfd3821e 100644 --- a/bsc-plugin/src/lib/rooibos/Annotation.ts +++ b/bsc-plugin/src/lib/rooibos/Annotation.ts @@ -219,4 +219,4 @@ export class RooibosAnnotation { export function getAnnotationType(text: string): AnnotationType { return annotationLookup[text.toLowerCase()] || AnnotationType.None; -} \ No newline at end of file +} diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts index 8f963933..13ae670a 100644 --- a/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts +++ b/bsc-plugin/src/lib/rooibos/MockUtil.spec.ts @@ -85,6 +85,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.sayhello __stubOrMockResult = __stubFunction(a1, a2) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("sayhello") + value = __stubs_globalAa.__globalStubs.sayhello + return value end if print "hello" end function @@ -122,6 +125,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.sayhello __stubOrMockResult = __stubFunction(a1, a2) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("sayhello") + value = __stubs_globalAa.__globalStubs.sayhello + return value end if print "hello" end function @@ -162,6 +168,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.redlines_setrulerlines __stubOrMockResult = __stubFunction(rulerLines) return + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("redlines_setrulerlines") + value = __stubs_globalAa.__globalStubs.redlines_setrulerlines + return end if For Each line In rulerLines.Items() RedLines_AddLine(line.key, line.value.position, line.value.coords, m.node, m.childMap) @@ -177,6 +186,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.redlines_addline __stubOrMockResult = __stubFunction(id, position, coords, node, childMap) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("redlines_addline") + value = __stubs_globalAa.__globalStubs.redlines_addline + return value end if line = CreateObject("roSGNode", "Rectangle") line.setField("id", id) @@ -213,6 +225,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.sayhello __stubOrMockResult = __stubFunction(a1, a2) return + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("sayhello") + value = __stubs_globalAa.__globalStubs.sayhello + return end if print "hello" end sub @@ -250,6 +265,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello __stubOrMockResult = __stubFunction(a1, a2) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("person_utils_sayhello") + value = __stubs_globalAa.__globalStubs.person_utils_sayhello + return value end if print "hello" end function @@ -287,6 +305,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.person_utils_sayhello __stubOrMockResult = __stubFunction(a1, a2) return + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("person_utils_sayhello") + value = __stubs_globalAa.__globalStubs.person_utils_sayhello + return end if print "hello" end sub @@ -378,6 +399,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.beings_sayhello __stubOrMockResult = __stubFunction() return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("beings_sayhello") + value = __stubs_globalAa.__globalStubs.beings_sayhello + return value end if print "hello2" end function @@ -391,6 +415,9 @@ describe('MockUtil', () => { __stubFunction = __stubs_globalAa.__globalStubs.sayhello __stubOrMockResult = __stubFunction() return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("sayhello") + value = __stubs_globalAa.__globalStubs.sayhello + return value end if print "hello3" end function diff --git a/bsc-plugin/src/lib/rooibos/MockUtil.ts b/bsc-plugin/src/lib/rooibos/MockUtil.ts index fa66034a..525251b7 100644 --- a/bsc-plugin/src/lib/rooibos/MockUtil.ts +++ b/bsc-plugin/src/lib/rooibos/MockUtil.ts @@ -107,11 +107,14 @@ export class MockUtil { ${globalAaName} = getGlobalAa() if RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"] <> invalid ${resultName} = RBS_SM_${this.fileId}_getMocksByFunctionName()["${methodName}"].callback(${paramNames}) - return${requiresReturnValue ? ` ${resultName}` : '' } + return${requiresReturnValue ? ` ${resultName}` : ''} else if type(${globalAaName}?.${storageName}?.${methodName}).endsWith("Function") __stubFunction = ${globalAaName}.${storageName}.${methodName} ${resultName} = __stubFunction(${paramNames}) return${requiresReturnValue ? ` ${resultName}` : ''} + else if ${globalAaName}?.${storageName} <> invalid and ${globalAaName}.${storageName}.doesExist("${methodName}") + value = ${globalAaName}.${storageName}.${methodName} + return${requiresReturnValue ? ` value` : ''} end if `; const astCodeToInject = Parser.parse(template).ast.statements; diff --git a/bsc-plugin/src/lib/rooibos/RooibosConfig.ts b/bsc-plugin/src/lib/rooibos/RooibosConfig.ts index 6d5cd16b..af5cdc68 100644 --- a/bsc-plugin/src/lib/rooibos/RooibosConfig.ts +++ b/bsc-plugin/src/lib/rooibos/RooibosConfig.ts @@ -24,7 +24,12 @@ export interface RooibosConfig { catchCrashes?: boolean; throwOnFailedAssertion?: boolean; sendHomeOnFinish?: boolean; + + /** + * @deprecated Use the `reporters` array instead + */ reporter?: string; + reporters?: string[]; keepAppOpen?: boolean; testSceneName?: string; diff --git a/bsc-plugin/src/lib/rooibos/RooibosSession.ts b/bsc-plugin/src/lib/rooibos/RooibosSession.ts index 1b44dd86..7d636229 100644 --- a/bsc-plugin/src/lib/rooibos/RooibosSession.ts +++ b/bsc-plugin/src/lib/rooibos/RooibosSession.ts @@ -137,7 +137,7 @@ export class RooibosSession { method.func.body.statements.length, Parser.parse(undent` return { - "reporter": "${this.config.reporter || ''}" + "reporters": ${this.getReportersList()} "failFast": ${this.config.failFast ? 'true' : 'false'} "sendHomeOnFinish": ${this.config.sendHomeOnFinish ? 'true' : 'false'} "logLevel": ${this.config.logLevel ?? 0} @@ -156,6 +156,30 @@ export class RooibosSession { } } + getReportersList() { + let reporters = this.config.reporters; + if (!Array.isArray(reporters)) { + reporters = []; + } + if (this.config.reporter) { + // @todo: warn that `reporter` is deprecated and to use `reporters` instead + reporters.push(this.config.reporter); + } + if (reporters.length < 1) { + reporters.push('console'); + } + return `[${reporters.map(this.sanitiseReporterName).toString()}]`; + } + + sanitiseReporterName(name: string) { + switch (name.toLowerCase()) { + case 'console': return 'rooibos_ConsoleTestReporter'; + case 'junit': return 'rooibos_JUnitTestReporter'; + } + // @todo: check if function name is valid + return name; + } + updateVersionTextFunction(classStatement: ClassStatement, editor: AstEditor) { let method = classStatement.methods.find((m) => m.name.text === 'getVersionText'); if (method) { diff --git a/bsc-plugin/src/lib/rooibos/Utils.ts b/bsc-plugin/src/lib/rooibos/Utils.ts index 39bd00a8..7b3d788e 100644 --- a/bsc-plugin/src/lib/rooibos/Utils.ts +++ b/bsc-plugin/src/lib/rooibos/Utils.ts @@ -107,7 +107,7 @@ export function getPathValuePartAsString(expr: Expression) { export function getScopeForSuite(testSuite: TestSuite) { if (testSuite.isNodeTest) { - return testSuite.file.program.getScopesForFile(testSuite.file).find((scope)=> { + return testSuite.file.program.getScopesForFile(testSuite.file).find((scope) => { return isXmlScope(scope) && scope.xmlFile.componentName.text === testSuite.generatedNodeName; }); diff --git a/bsc-plugin/src/plugin.spec.ts b/bsc-plugin/src/plugin.spec.ts index b504dde4..6163e8a3 100644 --- a/bsc-plugin/src/plugin.spec.ts +++ b/bsc-plugin/src/plugin.spec.ts @@ -15,21 +15,13 @@ describe('RooibosPlugin', () => { let program: Program; let builder: ProgramBuilder; let plugin: RooibosPlugin; - let options; - beforeEach(() => { - plugin = new RooibosPlugin(); - options = { - rootDir: _rootDir, - stagingFolderPath: _stagingFolderPath, - stagingDir: _stagingFolderPath, - rooibos: { - isGlobalMethodMockingEnabled: true - } - }; + + function setupProgram(options) { fsExtra.emptyDirSync(tmpPath); fsExtra.ensureDirSync(_stagingFolderPath); fsExtra.ensureDirSync(_rootDir); + plugin = new RooibosPlugin(); builder = new ProgramBuilder(); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument builder.options = util.normalizeAndResolveConfig(options); @@ -40,13 +32,28 @@ describe('RooibosPlugin', () => { plugin.beforeProgramCreate(builder); plugin.fileFactory['options'].frameworkSourcePath = path.resolve(path.join('../framework/src/source')); plugin.afterProgramCreate(program); - }); + } - afterEach(() => { + function destroyProgram() { fsExtra.ensureDirSync(tmpPath); fsExtra.emptyDirSync(tmpPath); builder.dispose(); program.dispose(); + } + + beforeEach(() => { + setupProgram({ + rootDir: _rootDir, + stagingFolderPath: _stagingFolderPath, + stagingDir: _stagingFolderPath, + rooibos: { + isGlobalMethodMockingEnabled: true + } + }); + }); + + afterEach(() => { + destroyProgram(); }); describe('basic tests', () => { @@ -594,29 +601,15 @@ describe('RooibosPlugin', () => { it('adds launch hook with custom scene', async () => { - options = { + setupProgram({ rootDir: _rootDir, stagingFolderPath: _stagingFolderPath, stagingDir: _stagingFolderPath, rooibos: { testSceneName: 'CustomRooibosScene' } - }; - plugin = new RooibosPlugin(); - fsExtra.ensureDirSync(_stagingFolderPath); - fsExtra.ensureDirSync(_rootDir); - fsExtra.ensureDirSync(tmpPath); - - builder = new ProgramBuilder(); - builder.options = util.normalizeAndResolveConfig(options); - builder.program = new Program(builder.options); - program = builder.program; - program.plugins.add(plugin); - program.createSourceScope(); //ensure source scope is created - plugin.beforeProgramCreate(builder); - plugin.fileFactory['options'].frameworkSourcePath = path.resolve(path.join('../framework/src/source')); - plugin.afterProgramCreate(program); - // program.validate(); + }); + const file = program.setFile('source/main.bs', ` sub main() print "main" @@ -1070,6 +1063,9 @@ describe('RooibosPlugin', () => { __stubFunction = __stubs_globalAa.__globalStubs.sayhello __stubOrMockResult = __stubFunction(firstName, lastName) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("sayhello") + value = __stubs_globalAa.__globalStubs.sayhello + return value end if print firstName + " " + lastName end function @@ -1158,6 +1154,9 @@ describe('RooibosPlugin', () => { __stubFunction = __stubs_globalAa.__globalStubs.utils_sayhello __stubOrMockResult = __stubFunction(firstName, lastName) return __stubOrMockResult + else if __stubs_globalAa?.__globalStubs <> invalid and __stubs_globalAa.__globalStubs.doesExist("utils_sayhello") + value = __stubs_globalAa.__globalStubs.utils_sayhello + return value end if print firstName + " " + lastName end function @@ -1908,34 +1907,14 @@ describe('RooibosPlugin', () => { `; beforeEach(() => { - plugin = new RooibosPlugin(); - options = { + setupProgram({ rootDir: _rootDir, stagingFolderPath: _stagingFolderPath - }; - fsExtra.ensureDirSync(_stagingFolderPath); - fsExtra.ensureDirSync(_rootDir); - fsExtra.ensureDirSync(tmpPath); - - builder = new ProgramBuilder(); - builder.options = util.normalizeAndResolveConfig(options); - builder.program = new Program(builder.options); - program = builder.program; - builder.program = new Program(builder.options); - program = builder.program; - program.plugins.add(plugin); - program.createSourceScope(); //ensure source scope is created - plugin.beforeProgramCreate(builder); - plugin.fileFactory['options'].frameworkSourcePath = path.resolve(path.join('../framework/src/source')); - plugin.afterProgramCreate(program); - // program.validate(); + }); }); afterEach(() => { - fsExtra.ensureDirSync(tmpPath); - fsExtra.emptyDirSync(tmpPath); - builder.dispose(); - program.dispose(); + destroyProgram(); }); it('tag one', async () => { @@ -2088,9 +2067,9 @@ describe('RooibosPlugin', () => { expect(findMethod('getIgnoredTestInfo').func.body.statements).to.be.empty; await builder.transpile(); - let testContents = getTestFunctionContents(); + expect( - testContents + getTestFunctionContents() ).to.eql(undent` item = { id: "item" @@ -2109,7 +2088,6 @@ describe('RooibosPlugin', () => { end if `); - let a = getContents('rooibos/RuntimeConfig.brs'); expect( getContents('rooibos/RuntimeConfig.brs') ).to.eql(undent` @@ -2122,7 +2100,9 @@ describe('RooibosPlugin', () => { end function instance.getRuntimeConfig = function() return { - "reporter": "" + "reporters": [ + rooibos_ConsoleTestReporter + ] "failFast": true "sendHomeOnFinish": true "logLevel": 0 @@ -2174,6 +2154,85 @@ describe('RooibosPlugin', () => { expect(findMethod('getAllTestSuitesNames').func.body.statements).to.be.empty; expect(findMethod('getIgnoredTestInfo').func.body.statements).to.be.empty; }); + + const sep = '\n '; + const params: [string[], string][] = [ + [[], 'rooibos_ConsoleTestReporter'], + [['CONSOLE'], 'rooibos_ConsoleTestReporter'], + [['MyCustomReporter'], 'MyCustomReporter'], + [['JUnit', 'MyCustomReporter'], `rooibos_JUnitTestReporter${sep}MyCustomReporter`] + ]; + it('adds custom test reporters', async () => { + for (const [reporters, expected] of params) { + setupProgram({ + rootDir: _rootDir, + stagingFolderPath: _stagingFolderPath, + stagingDir: _stagingFolderPath, + rooibos: { + reporters: reporters + } + }); + + program.validate(); + expect(program.getDiagnostics()).to.be.empty; + + await builder.transpile(); + + expect( + getContents('rooibos/RuntimeConfig.brs') + ).to.eql(undent` + function __rooibos_RuntimeConfig_builder() + instance = {} + instance.new = sub() + end sub + instance.getVersionText = function() + return "${version}" + end function + instance.getRuntimeConfig = function() + return { + "reporters": [ + ${expected} + ] + "failFast": true + "sendHomeOnFinish": true + "logLevel": 0 + "showOnlyFailures": true + "printTestTimes": true + "lineWidth": 60 + "printLcov": false + "port": "invalid" + "catchCrashes": true + "throwOnFailedAssertion": false + "keepAppOpen": true + "isRecordingCodeCoverage": false + } + end function + instance.getTestSuiteClassWithName = function(name) + if false + ? "noop" + end if + end function + instance.getAllTestSuitesNames = function() + return [] + end function + instance.getIgnoredTestInfo = function() + return { + "count": 0 + "items": [] + } + end function + return instance + end function + function rooibos_RuntimeConfig() + instance = __rooibos_RuntimeConfig_builder() + instance.new() + return instance + end function + `); + + destroyProgram(); + } + }); }); describe.skip('run a local project', () => { diff --git a/bsconfig-prod.json b/bsconfig-prod.json new file mode 100644 index 00000000..2a748abc --- /dev/null +++ b/bsconfig-prod.json @@ -0,0 +1,14 @@ +{ + "rootDir": "framework/src", + "files": ["manifest", "source/**/*.*", "components/**/*.*"], + "autoImportComponentScript": true, + "createPackage": false, + "stagingFolderPath": "dist", + "diagnosticFilters": [ + { + "src": "**/roku_modules/**/*.*", + "codes": [1107, 1009] + } + ], + "plugins": ["@rokucommunity/bslint"] +} diff --git a/docs/index.md b/docs/index.md index 720df32d..f158c89b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -172,7 +172,11 @@ 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"]`. | +**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/georgejecook/rooibos/issues/266)). ## Creating test suites diff --git a/framework/src/source/BaseTestReporter.bs b/framework/src/source/BaseTestReporter.bs index 2f114272..15db20ef 100644 --- a/framework/src/source/BaseTestReporter.bs +++ b/framework/src/source/BaseTestReporter.bs @@ -1,4 +1,8 @@ namespace rooibos + interface ITestReporterOnEndEvent + stats as rooibos.Stats + end interface + class BaseTestReporter public testRunner = invalid @@ -11,15 +15,11 @@ namespace rooibos m.allStats = runner.stats end function - function reportResults(allStats as dynamic) - 'override me - end function - - function testLogInfo(text as string) + function onBegin(ev as dynamic) 'override me end function - function testLogError(text as string) + function onEnd(ev as rooibos.ITestReporterOnEndEvent) 'override me end function diff --git a/framework/src/source/BaseTestSuite.bs b/framework/src/source/BaseTestSuite.bs index 5a031091..da40806e 100644 --- a/framework/src/source/BaseTestSuite.bs +++ b/framework/src/source/BaseTestSuite.bs @@ -199,6 +199,8 @@ namespace rooibos m.testRunner.top.unobserveFieldScoped("rooibosGroupFinished") ' m.testGroupDone() m.onAsyncGroupComplete(group) + else if m.testRunner.top.rooibosGroupFinished + m.onAsyncGroupComplete(group) end if end if @@ -1833,7 +1835,7 @@ namespace rooibos end if if not type(stubOrReturnValue).endsWith("Function") - throw "Did not provide a stub function" + ' throw "Did not provide a stub function" end if ' Store the stub on the component scope diff --git a/framework/src/source/CommonUtils.bs b/framework/src/source/CommonUtils.bs index 9b9a6f51..38451201 100755 --- a/framework/src/source/CommonUtils.bs +++ b/framework/src/source/CommonUtils.bs @@ -334,7 +334,9 @@ namespace rooibos.common 'bs:disable-next-line isFirst = false end if - for each key in input + keys = input.keys() + keys.sort() + for each key in keys if rooibos.common.canSafelyIterateAAKey(input, key) text = text + key + ":" + rooibos.common.asString(input[key], includeType) end if diff --git a/framework/src/source/ConsoleTestReporter.bs b/framework/src/source/ConsoleTestReporter.bs index d0e609f4..d33abb0c 100644 --- a/framework/src/source/ConsoleTestReporter.bs +++ b/framework/src/source/ConsoleTestReporter.bs @@ -13,8 +13,8 @@ namespace rooibos end if end function - override function reportResults(allStats) - m.allStats = allStats + override function onEnd(ev as rooibos.ITestReporterOnEndEvent) + m.allStats = ev.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) @@ -186,14 +186,6 @@ namespace rooibos '++ printing '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - override function testLogInfo(text) - ? "INFO " ; text - end function - - override function testLogError(text) - ? "ERROR " ; text - end function - function printLine(depth = 0, text = "") ? " " ; text end function diff --git a/framework/src/source/JUnitTestReporter.bs b/framework/src/source/JUnitTestReporter.bs index d7320c34..4fe3346a 100644 --- a/framework/src/source/JUnitTestReporter.bs +++ b/framework/src/source/JUnitTestReporter.bs @@ -1,11 +1,7 @@ namespace rooibos class JUnitTestReporter extends rooibos.BaseTestReporter - function new(testRunner as dynamic) - super(testRunner) - end function - - override function reportResults(allStats as dynamic) + override function onEnd(ev as rooibos.ITestReporterOnEndEvent) root = createObject("roXMLElement") root.SetName("testsuites") properties = root.addElement("properties") diff --git a/framework/src/source/Rooibos.bs b/framework/src/source/Rooibos.bs index 91c08249..03b1108e 100644 --- a/framework/src/source/Rooibos.bs +++ b/framework/src/source/Rooibos.bs @@ -36,6 +36,9 @@ namespace rooibos ? "keepAppOpen is false; exiting Rooibos" ' End statement will also exit the caller of this function ' leading to an instant exit of the application + + ' Give the io port time to finish sending all the logs + sleep(400) end end if end if @@ -91,4 +94,4 @@ namespace rooibos return 0 end function -end namespace \ No newline at end of file +end namespace diff --git a/framework/src/source/TestRunner.bs b/framework/src/source/TestRunner.bs index 05f183a2..ee5d6dfd 100644 --- a/framework/src/source/TestRunner.bs +++ b/framework/src/source/TestRunner.bs @@ -11,7 +11,7 @@ namespace rooibos ' */ class TestRunner public testScene = invalid - public testReporter = invalid + public testReporters = [] public nodeContext = invalid public config = invalid public testSuites = [] @@ -33,11 +33,7 @@ namespace rooibos m.runtimeConfig = new rooibos.RuntimeConfig() m.config = m.runtimeConfig.getRuntimeConfig() - if m.config.reporter = "JUnitTestReporter" - m.testReporter = new Rooibos.JUnitTestReporter(m) - else - m.testReporter = new Rooibos.ConsoleTestReporter(m) - end if + m.testReporters = m.getTestReporters() end function ' /** @@ -49,6 +45,12 @@ namespace rooibos ' */ public function run() + for each reporter in m.testReporters + if rooibos.common.isFunction(reporter.onBegin) + reporter.onBegin({}) + end if + end for + rooibosTimer = createObject("roTimespan") rooibosTimer.mark() suiteNames = m.runtimeConfig.getAllTestSuitesNames() @@ -103,7 +105,11 @@ namespace rooibos m.stats.time = rooibosTimer.totalMilliseconds() - m.testReporter.reportResults(m.stats) + for each reporter in m.testReporters + if rooibos.common.isFunction(reporter.onEnd) + reporter.onEnd({ stats: m.stats }) + end if + end for rooibosResult = { stats: m.stats @@ -157,6 +163,7 @@ namespace rooibos ? "Running suite asynchronously!" m.nodeContext.top.observeFieldScoped("rooibosSuiteFinished", "Rooibos_onTestSuiteComplete") testSuite.run() + return invalid else ? "Running suite synchronously!" testSuite.run() @@ -307,6 +314,19 @@ namespace rooibos ut.PostFromString("") end function + private function getTestReporters() + reporters = [] + for each factory in m.config.reporters + if rooibos.common.isFunction(factory) + reporters.push(factory(m)) + end if + end for + if reporters.isEmpty() + reporters.push(new rooibos.ConsoleTestReporter(m)) + end if + return reporters + end function + end class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/tests/.vscode/launch.json b/tests/.vscode/launch.json index 728cb81a..26f48708 100644 --- a/tests/.vscode/launch.json +++ b/tests/.vscode/launch.json @@ -30,7 +30,7 @@ "stopDebuggerOnAppExit": true, "enableVariablesPanel": false, "injectRaleTrackerTask": false, - "enableDebugProtocol": false + "enableDebugProtocol": true }, { "name": "watch tests", @@ -61,7 +61,7 @@ "stopDebuggerOnAppExit": true, "enableVariablesPanel": false, "injectRaleTrackerTask": false, - "enableDebugProtocol": false + "enableDebugProtocol": true, } ] } diff --git a/tests/bsconfig.json b/tests/bsconfig.json index 08ba2e32..891684c7 100644 --- a/tests/bsconfig.json +++ b/tests/bsconfig.json @@ -27,7 +27,7 @@ ], "rooibos": { "showOnlyFailures": true, - "catchCrashes": true, + "catchCrashes": false, "lineWidth": 70, "failFast": false, "sendHomeOnFinish": false, diff --git a/tests/package-lock.json b/tests/package-lock.json index 7eff43d1..b8765bdc 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -166,6 +166,16 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "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/@xml-tools/parser": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", @@ -1658,6 +1668,13 @@ "node": ">=4.2.0" } }, + "node_modules/undici-types": { + "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 + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -2022,6 +2039,16 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "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" + } + }, "@xml-tools/parser": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", @@ -3183,6 +3210,13 @@ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "undici-types": { + "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 + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", diff --git a/tests/src/source/Charlies-Issues.spec.bs b/tests/src/source/Charlies-Issues.spec.bs new file mode 100644 index 00000000..889f2913 --- /dev/null +++ b/tests/src/source/Charlies-Issues.spec.bs @@ -0,0 +1,61 @@ +namespace tests + @suite + class MockExample extends rooibos.BaseTestSuite + + protected override function setUp() + super.setUp() + end function + + protected override function beforeEach() + m.SUT = SomeModel() + end function + + protected override function afterEach() + m.cleanMocks() + m.cleanStubs() + m.SUT = invalid + end function + + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @describe("Example Tests on SomeModel public methods") + '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + @it("globalMockExample1_passing") + function _() + m.stubCall(getManifestValue, function(key) : return "stubCall hardcoded title" : end function) + m.SUT.globalMockExample() + m.assertEqual(m.SUT.appTitle, "stubCall hardcoded title") + end function + + @it("globalMockExample2_crashing_due_to_returnValue") + @params("stubCall dynamic title") + function _(manifestValue as string) + m.stubCall(getManifestValue, manifestValue) + m.SUT.globalMockExample() + m.assertEqual(m.SUT.appTitle, manifestValue) + end function + + @it("stubExample1_passing") + @params({ configSetVia_config: true }, { configSetViaStub: true }) + function _(config1 as object, config2 as object) + m.SUT._config = config1 + m.stub(m.SUT, "_getConfig", config2) + m.SUT.setUpdatedConfig1() + expectedKeyNameConfig2 = config2.keys()[0] + resultKeyName = m.SUT.updatedConfig.keys()[0] + m.assertEqual(expectedKeyNameConfig2, resultKeyName) + end function + + @ignore("this is not supported - we do not support mocking singleton objects between test runs") + @it("stubExample2_crashing_stub_from_stubExample1_persisting") + @params({ configSetVia_config: true }, { configSetViaStub: true }) + function _(config1 as object, config2 as object) + m.SUT._config = config1 + m.SUT.setUpdatedConfig2() + expectedKeyNameConfig1 = config1.keys()[0] + resultKeyName = m.SUT.updatedConfig.keys()[0] + m.assertEqual(expectedKeyNameConfig1, resultKeyName) + end function + + end class +end namespace \ No newline at end of file diff --git a/tests/src/source/SomeModel.brs b/tests/src/source/SomeModel.brs new file mode 100644 index 00000000..116f4b2d --- /dev/null +++ b/tests/src/source/SomeModel.brs @@ -0,0 +1,34 @@ +function SomeModel() as object + if (m._someModel = invalid) + + obj = {} + obj.appTitle = "" + obj._config = invalid + obj.updatedConfig = invalid + + obj.globalMockExample = sub() + appTitle = getManifestValue("title") + m.appTitle = appTitle + end sub + + obj.setUpdatedConfig1 = sub() + m.updatedConfig = m._getConfig() + end sub + + obj.setUpdatedConfig2 = sub() + m.updatedConfig = m._getConfig() + end sub + + obj._getConfig = function() as object + return m._config + end function + + m._someModel = obj + end if + + return m._someModel +end function + +function getManifestValue(key as string) as string + return createObject("roAppInfo").getValue(key) +end function \ No newline at end of file