Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/rokucommunity/rooibos int…
Browse files Browse the repository at this point in the history
…o bugfix/only-annotation-not-always-applied-correctly
  • Loading branch information
TwitchBronBron committed Jan 28, 2025
2 parents c461df0 + e68b898 commit 7dc95be
Show file tree
Hide file tree
Showing 25 changed files with 1,558 additions and 220 deletions.
914 changes: 903 additions & 11 deletions bsc-plugin/package-lock.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion bsc-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"publish-npm:beta": "npm run test && npm publish --tag=beta",
"local": "ts-node scripts/install-local.js",
"remote": "ts-node scripts/install-npm.js",
"cli": "ts-node src/cli.ts"
"cli": "ts-node src/cli.ts",
"prepack": "node scripts/pack.js --pre",
"postpack": "node scripts/pack.js --post",
"postinstall": "ropm copy"
},
"repository": {
"type": "git",
Expand All @@ -35,6 +38,7 @@
"dependencies": {
"roku-debug": "^0.21.10",
"roku-deploy": "^3.12.1",
"rooibos_promises": "npm:@rokucommunity/promises@^0.5.0",
"source-map": "^0.7.3",
"undent": "^0.1.0",
"vscode-languageserver": "~6.1.1",
Expand Down Expand Up @@ -63,6 +67,7 @@
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^17.6.0",
"ropm": "^0.10.30",
"source-map-support": "^0.5.13",
"trim-whitespace": "^1.3.3",
"ts-node": "^9.0.0",
Expand Down Expand Up @@ -121,6 +126,9 @@
"ts"
]
},
"ropm": {
"rootDir": "../framework/src"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
Expand Down
20 changes: 20 additions & 0 deletions bsc-plugin/scripts/pack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var fsExtra = require('fs-extra');
var path = require('path');

var cachePath = path.join(__dirname, 'pack-cache.txt');
var packageJsonPath = path.join(__dirname, '../package.json');

var packageJson = fsExtra.readJsonSync(packageJsonPath);

//store the script and remove it from package.json
if (process.argv.includes('--pre')) {
fsExtra.outputFileSync(cachePath, packageJson.scripts.postinstall);
delete packageJson.scripts.postinstall;

//restore the script
} else if (process.argv.includes('--post')) {
packageJson.scripts.postinstall = fsExtra.readFileSync(cachePath, packageJson.scripts.postinstall).toString();
fsExtra.removeSync(cachePath);
}

fsExtra.outputJsonSync(packageJsonPath, packageJson, { spaces: 4 });
13 changes: 9 additions & 4 deletions bsc-plugin/src/lib/rooibos/Annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,18 @@ export class RooibosAnnotation {
isSolo = getAnnotationsOfType(AnnotationType.Solo).length > 0;
isIgnore = getAnnotationsOfType(AnnotationType.Ignore).length > 0;

for (const annotation of getAnnotationsOfType(AnnotationType.NodeTest)) {
nodeName = annotation.getArguments()[0] as string;
}

for (const annotation of getAnnotationsOfType(AnnotationType.Async)) {
async = true;
asyncTimeout = annotation.getArguments().length === 1 ? parseInt(annotation.getArguments()[0] as any) : -1;
}

for (const annotation of getAnnotationsOfType(AnnotationType.NodeTest)) {
nodeName = annotation.getArguments()[0] as string;
if (nodeName === null) {
// If the test is async, it must be a node test
// Default to `Node` if no node is specified
nodeName = 'Node';
}
}

for (const annotation of getAnnotationsOfType(AnnotationType.Tags)) {
Expand Down
18 changes: 14 additions & 4 deletions bsc-plugin/src/lib/rooibos/FileFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class FileFactory {
this.coverageComponentBrsTemplate = fs.readFileSync(path.join(this.frameworkSourcePath, '/source/rooibos/CodeCoverage.brs'), 'utf8');
}

public addedSourceFrameworkFilePaths: string[] = [];
public sourceFilesToAutoImport: string[] = [];
public addedFrameworkFiles: BscFile[] = [];

public addFrameworkFiles(program: Program) {
Expand All @@ -43,10 +43,10 @@ export class FileFactory {
});

for (let filePath of globedFiles) {
if (/^source[/\\]rooibos[/\\]/g.test(filePath)) {
if (this.shouldAddFileToImportList(filePath)) {
// Save a list of all source files added to the program
// to be imported by node test components
this.addedSourceFrameworkFilePaths.push(filePath);
this.sourceFilesToAutoImport.push(filePath);
}
let sourcePath = path.resolve(this.frameworkSourcePath, filePath);
let fileContents = fs.readFileSync(sourcePath, 'utf8').toString();
Expand All @@ -67,7 +67,7 @@ export class FileFactory {

public createTestXML(name: string, baseName: string, suite?: TestSuite): string {
let scriptImports = [];
for (let filePath of this.addedSourceFrameworkFilePaths) {
for (let filePath of this.sourceFilesToAutoImport) {
scriptImports.push(`<script type="text/brighterscript" uri="pkg:/${filePath}" />`);
}

Expand Down Expand Up @@ -117,6 +117,16 @@ export class FileFactory {
return result !== undefined;
}

private shouldAddFileToImportList(destFilePath): boolean {
const pathDetails = path.parse(destFilePath);
if (pathDetails.dir === 'source' || pathDetails.dir.startsWith('source\\') || pathDetails.dir.startsWith('source/')) {
if (pathDetails.ext === '.brs' || (pathDetails.ext === '.bs' && !pathDetails.name.endsWith('.d'))) {
return true;
}
}
return false;
}

public addFile(program, projectPath: string, contents: string) {
try {
const file = program.setFile({
Expand Down
6 changes: 6 additions & 0 deletions bsc-plugin/src/lib/rooibos/RooibosSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ export class RooibosSession {
}
let brsFile = this.fileFactory.addFile(program, suite.bsPkgPath, undent`
function init()
m.top.addField("rooibosRunSuite", "boolean", false)
m.top.observeFieldScoped("rooibosRunSuite", "rooibosRunSuite")
end function
function rooibosRunSuite()
m.top.unobserveFieldScoped("rooibosRunSuite")
nodeRunner = Rooibos_TestRunner(m.top.getScene(), m)
m.top.rooibosTestResult = nodeRunner.runInNodeMode("${suite.name}")
end function
Expand Down
1 change: 1 addition & 0 deletions bsc-plugin/src/lib/rooibos/TestGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export class TestGroup extends TestBlock {
name: ${sanitizeBsJsonString(this.name)}
isSolo: ${this.isSolo}
isIgnored: ${this.isIgnored}
isAsync: ${this.isAsync}
filename: "${this.pkgPath}"
lineNumber: "${this.annotation.annotation.range.start.line + 1}"
setupFunctionName: "${this.setupFunctionName || ''}"
Expand Down
6 changes: 5 additions & 1 deletion bsc-plugin/src/lib/rooibos/TestSuite.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from 'path';
import type { AstEditor, BrsFile, ClassStatement } from 'brighterscript';
import { nodes } from 'brighterscript/dist/roku-types';

import { diagnosticNodeTestIllegalNode, diagnosticNodeTestRequiresNode } from '../utils/Diagnostics';

Expand All @@ -9,6 +10,8 @@ import type { TestGroup } from './TestGroup';
import { addOverriddenMethod, sanitizeBsJsonString } from './Utils';
import type { RooibosSession } from './RooibosSession';

const nativeNodeNames = Object.keys(nodes);

/**
* base of test suites and blocks..
*/
Expand Down Expand Up @@ -116,7 +119,7 @@ export class TestSuite extends TestBlock {
if (this.isNodeTest) {
if (!this.nodeName) {
diagnosticNodeTestRequiresNode(this.file, this.annotation.annotation);
} else if (!this.file.program.getComponent(this.nodeName)) {
} else if (!this.file.program.getComponent(this.nodeName) && !nativeNodeNames.includes(this.nodeName.toLowerCase())) {
diagnosticNodeTestIllegalNode(this.file, this.annotation.annotation, this.nodeName);
}
}
Expand All @@ -130,6 +133,7 @@ export class TestSuite extends TestBlock {
isSolo: ${this.isSolo}
noCatch: ${this.annotation.noCatch}
isIgnored: ${this.isIgnored}
isAsync: ${this.isAsync}
pkgPath: "${this.pkgPath}"
filePath: "${this.filePath}"
lineNumber: ${this.classStatement.range.start.line + 1}
Expand Down
3 changes: 3 additions & 0 deletions bsc-plugin/src/lib/rooibos/TestSuiteBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ export class TestSuiteBuilder {
}

private sanitizeFunctionName(name: string) {
if (/^\d/.test(name)) {
name = '_' + name;
}
return name.replace(/[^0-9_a-z]/ig, '_');
}
public createTestCases(statement: ClassMethodStatement, annotation: RooibosAnnotation): boolean {
Expand Down
123 changes: 122 additions & 1 deletion bsc-plugin/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ describe('RooibosPlugin', () => {
isSolo: false
noCatch: false
isIgnored: false
isAsync: false
pkgPath: "${s`source/test.spec.bs`}"
filePath: "${s`${tmpPath}/rootDir/source/test.spec.bs`}"
lineNumber: 3
Expand All @@ -729,6 +730,7 @@ describe('RooibosPlugin', () => {
name: "groupA"
isSolo: false
isIgnored: false
isAsync: false
filename: "${s`source/test.spec.bs`}"
lineNumber: "4"
setupFunctionName: ""
Expand Down Expand Up @@ -775,6 +777,124 @@ describe('RooibosPlugin', () => {
})).not.to.exist;
});

it('handles groups that start with numbers', async () => {
plugin.afterProgramCreate(program);
// program.validate();
const file = program.setFile<BrsFile>('source/test.spec.bs', `
@suite
class ATest extends rooibos.BaseTestSuite
@describe("1groupA")
@it("is test1")
@slow(1000)
function Test_3()
end function
end class
`);
program.validate();
await builder.transpile();
console.log(builder.getDiagnostics());
expect(builder.getDiagnostics()).to.have.length(1);
expect(builder.getDiagnostics()[0].severity).to.equal(DiagnosticSeverity.Warning);
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
expect(plugin.session.sessionInfo.suitesCount).to.equal(1);
expect(plugin.session.sessionInfo.groupsCount).to.equal(1);
expect(plugin.session.sessionInfo.testsCount).to.equal(1);

expect(
getContents('rooibosMain.brs')
).to.eql(undent`
function main()
Rooibos_init("RooibosScene")
end function
`);
expect(
getContents('test.spec.brs')
).to.eql(undent`
function __ATest_builder()
instance = __rooibos_BaseTestSuite_builder()
instance.super0_new = instance.new
instance.new = sub()
m.super0_new()
end sub
instance._1groupA_is_test1 = function()
end function
instance.super0_getTestSuiteData = instance.getTestSuiteData
instance.getTestSuiteData = function()
return {
name: "ATest"
isSolo: false
noCatch: false
isIgnored: false
isAsync: false
pkgPath: "${s`source/test.spec.bs`}"
filePath: "${s`${tmpPath}/rootDir/source/test.spec.bs`}"
lineNumber: 3
valid: true
hasFailures: false
hasSoloTests: false
hasIgnoredTests: false
hasSoloGroups: false
setupFunctionName: ""
tearDownFunctionName: ""
beforeEachFunctionName: ""
afterEachFunctionName: ""
isNodeTest: false
isAsync: false
asyncTimeout: 60000
nodeName: ""
generatedNodeName: "ATest"
testGroups: [
{
name: "1groupA"
isSolo: false
isIgnored: false
isAsync: false
filename: "${s`source/test.spec.bs`}"
lineNumber: "4"
setupFunctionName: ""
tearDownFunctionName: ""
beforeEachFunctionName: ""
afterEachFunctionName: ""
testCases: [
{
isSolo: false
noCatch: false
funcName: "_1groupA_is_test1"
isIgnored: false
isAsync: false
asyncTimeout: 2000
slow: 1000
isParamTest: false
name: "is test1"
lineNumber: 7
paramLineNumber: 0
assertIndex: 0
rawParams: invalid
paramTestIndex: 0
expectedNumberOfParams: 0
isParamsValid: true
}
]
}
]
}
end function
return instance
end function
function ATest()
instance = __ATest_builder()
instance.new()
return instance
end function
`);

//verify the AST was restored after transpile
const cls = file.ast.statements[0] as ClassStatement;
expect(cls.body.find((x: ClassMethodStatement) => {
return x.name?.text.toLowerCase() === 'getTestSuiteData'.toLowerCase();
})).not.to.exist;
});

it('test full transpile with complex params', async () => {
plugin.afterProgramCreate(program);
// program.validate();
Expand Down Expand Up @@ -2393,7 +2513,8 @@ describe('RooibosPlugin', () => {
[['mocha'], 'rooibos_MochaTestReporter'],
[['JUnit', 'MyCustomReporter'], `rooibos_JUnitTestReporter${sep}MyCustomReporter`]
];
it('adds custom test reporters', async () => {
it('adds custom test reporters', async function test() {
this.timeout(10_000);
for (const [reporters, expected] of params) {
setupProgram({
rootDir: _rootDir,
Expand Down
21 changes: 20 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ The behavior of your unit tests is identical to unit testing any other class, wi
### Async testing
To indicate a test suite will run in async mode you will mark the test suite with the @async annotation or mark one ore more tests with @async. This will cause rooibos to run the tests in async mode. If all tests in the suite do not complete within the timeout, then the suite will fail. The default time out is 60 seconds.

When you mark any test in a suite with the @async annotation this will keep the test running in the background, waiting for a call to m.done(). If the call is not made within the timeout, then the test fails. Again, the default timeout for each test is 2 seconds.
When you mark any test in a suite with the @async annotation this will keep the test running in the background, waiting for a call to `m.done()`. If the call is not made within the timeout, then the test fails. Again, the default timeout for each test is 2 seconds.

Note: you are not required to add @async to your testSuite, it is implied when you add it one more more tests. However, you may wish to add tge async annotation to your suite, to override the default of 60 seconds (e.g. with the annotation `async(12000)` we are instructing rooibos to wait up to 2 minutes for the _whole_ suite).

Expand Down Expand Up @@ -1142,7 +1142,26 @@ function OnTimer()
m.testSuite.done()
end function
```
#### Working with Promises

Alternately, instead of using the `done()` callback, you may return a `Promise` from your test. The test will automaticly complete when the promise is completed. If the returned promise rejects the test will be considered failed.

This is useful if the APIs you are testing return promises:

```
@it('respond with matching records')
function _()
return rooibos.promises.chain(db.find({type: 'User'})).then(sub(result)
m.testSuite.assertEqual(result.count(), 3)
end sub).catch(sub(error)
m.testSuite.fail("should not reject")
end sub).toPromises()
end function
```

Rooibos implements the [rokucomunity/promises](https://github.com/rokucommunity/promises) library to enable this support. Please visit that project for more details and examples of promises.

Note: `Promises` are only supported in Node tests and returning a promise from a non-Node test will automaticly fail the test.

## Advanced setup

Expand Down
2 changes: 1 addition & 1 deletion framework/src/source/rooibos/BaseTestReporter.bs
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ namespace rooibos
interface TestReporterOnEndEvent
stats as rooibos.Stats
end interface
end namespace
end namespace
Loading

0 comments on commit 7dc95be

Please sign in to comment.