Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/lib/rooibos/MockUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,37 @@ export class MockUtil {

gatherGlobalMethodMocks(testSuite: TestSuite) {
// console.log('gathering global method mocks for testSuite', testSuite.name);
const processedFuncs = new Set<string>();
for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
for (const hookName of [group.setupFunctionName, group.tearDownFunctionName, group.beforeEachFunctionName, group.afterEachFunctionName]) {
if (hookName) {
const key = hookName.toLowerCase();
if (!processedFuncs.has(key)) {
this.gatherMockedFunctionByName(testSuite, hookName);
processedFuncs.add(key);
}
}
}
for (let testCase of [...group.testCases].filter((tc) => tc.isIncluded)) {
this.gatherMockedGlobalMethods(testSuite, testCase);
const key = testCase.funcName.toLowerCase();
if (!processedFuncs.has(key)) {
this.gatherMockedGlobalMethods(testSuite, testCase);
processedFuncs.add(key);
}
}
}

}
private gatherMockedGlobalMethods(testSuite: TestSuite, testCase: TestCase) {
this.gatherMockedFunctionByName(testSuite, testCase.funcName);
}

private gatherMockedFunctionByName(testSuite: TestSuite, funcName: string) {
try {
let func = testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === testCase.funcName.toLowerCase());
let func = testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === funcName.toLowerCase());
if (!func) {
return;
}
func.walk(createVisitor({
ExpressionStatement: (expressionStatement, parent, owner) => {
let callExpression = expressionStatement.expression as CallExpression;
Expand Down
17 changes: 16 additions & 1 deletion src/lib/rooibos/TestGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,27 @@ export class TestGroup extends TestBlock {
}

public modifyAssertions(testCase: TestCase, noEarlyExit: boolean, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
let func = this.testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === testCase.funcName.toLowerCase());
this.modifyAssertionsInFunction(func, noEarlyExit, editor, namespaceLookup, scope);
}

public modifyAssertionsForHook(hookFuncName: string, noEarlyExit: boolean, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
if (!hookFuncName) {
return;
}
let func = this.testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === hookFuncName.toLowerCase());
if (!func) {
return;
}
this.modifyAssertionsInFunction(func, noEarlyExit, editor, namespaceLookup, scope);
}

private modifyAssertionsInFunction(func: any, noEarlyExit: boolean, editor: AstEditor, namespaceLookup: Map<string, NamespaceContainer>, scope: Scope) {
//for each method
//if assertion
//wrap with if is not fail
//add line number as last param
try {
let func = this.testSuite.classStatement.methods.find((m) => m.name.text.toLowerCase() === testCase.funcName.toLowerCase());
func.walk(createVisitor({
ExpressionStatement: (expressionStatement, parent, owner, key) => {
let callExpression = expressionStatement.expression as CallExpression;
Expand Down
214 changes: 214 additions & 0 deletions src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,220 @@ describe('RooibosPlugin', () => {
});
});

describe('transpilation in setup hooks', () => {
it('transpiles stubCall inside beforeEach', async () => {
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@beforeEach
function _be()
m.stubCall(m.thing.getFunction(), "return")
end function

@it("test1")
function _()
m.assertTrue(true)
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
await builder.transpile();
const fileContents = getContents('test.spec.brs');
expectFunctionContents(fileContents, '__ATest_method__be', `
m._stubCall(m.thing, "getFunction", m, "m.thing", "return")
`);
});

it('transpiles expectCalled inside beforeEach with early-exit guard', async () => {
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@beforeEach
function _be()
m.expectCalled(m.thing.getFunction("arg1"), "return")
end function

@it("test1")
function _()
m.assertTrue(true)
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
await builder.transpile();
const fileContents = getContents('test.spec.brs');
expectFunctionContents(fileContents, '__ATest_method__be', `
m.currentAssertLineNumber = 8
m._expectCalled(m.thing, "getFunction", m, "m.thing", [
"arg1"
], "return")
if m.currentResult?.isFail = true then
m.done()
return invalid
end if
`);
});

it('transpiles expectNotCalled inside afterEach', async () => {
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@afterEach
function _ae()
m.expectNotCalled(m.thing.getFunction())
end function

@it("test1")
function _()
m.assertTrue(true)
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
await builder.transpile();
const fileContents = getContents('test.spec.brs');
expectFunctionContents(fileContents, '__ATest_method__ae', `
m.currentAssertLineNumber = 8
m._expectNotCalled(m.thing, "getFunction", m, "m.thing")
if m.currentResult?.isFail = true then
m.done()
return invalid
end if
`);
});

it('transpiles stubCall inside setup and tearDown', async () => {
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@setup
function _su()
m.stubCall(m.thing.suFn(), "su-return")
end function

@tearDown
function _td()
m.stubCall(m.thing.tdFn(), "td-return")
end function

@it("test1")
function _()
m.assertTrue(true)
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
await builder.transpile();
const fileContents = getContents('test.spec.brs');
expectFunctionContents(fileContents, '__ATest_method__su', `
m._stubCall(m.thing, "suFn", m, "m.thing", "su-return")
`);
expectFunctionContents(fileContents, '__ATest_method__td', `
m._stubCall(m.thing, "tdFn", m, "m.thing", "td-return")
`);
});

it('registers global stub functions referenced only from a beforeEach', async () => {
destroyProgram();
setupProgram({
rootDir: _rootDir,
stagingFolderPath: _stagingFolderPath,
stagingDir: _stagingFolderPath,
rooibos: {
isGlobalMethodMockingEnabled: true,
isGlobalMethodMockingEfficientMode: true
}
});

program.setFile('source/code.bs', `
function globalFn()
return "real"
end function
`);
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@beforeEach
function _be()
m.stubCall(globalFn, function()
return "stubbed"
end function)
end function

@it("test1")
function _()
m.assertEqual(globalFn(), "stubbed")
end function
end class
`);
program.validate();
expect(program.getDiagnostics().filter((d) => d.code !== 'RBS2213')).to.be.empty;
await builder.transpile();
expect(plugin.session.globalStubbedMethods.has('globalfn')).to.be.true;
});

it('walks each hook method at most once even when reused across groups', async () => {
program.setFile('source/test.spec.bs', `
@suite
class ATest
@describe("groupA")

@beforeEach
function _be()
m.stubCall(m.thing.getFunction(), "return")
end function

@it("test1")
function _()
m.assertTrue(true)
end function

@describe("groupB")

@beforeEach
function _be2()
m.stubCall(m.thing.getFunction(), "return")
end function

@it("test2")
function _()
m.assertTrue(true)
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
await builder.transpile();
const fileContents = getContents('test.spec.brs');
// Each beforeEach should have been transpiled exactly once — if it were
// walked twice we'd see two m._stubCall(...) lines for the same call.
expectFunctionContents(fileContents, '__ATest_method__be', `
m._stubCall(m.thing, "getFunction", m, "m.thing", "return")
`);
expectFunctionContents(fileContents, '__ATest_method__be2', `
m._stubCall(m.thing, "getFunction", m, "m.thing", "return")
`);
});
});

describe('honours tags - simple tests', () => {
let testSource = `
@tags("one", "two", "exclude")
Expand Down
10 changes: 10 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,18 @@ export class RooibosPlugin implements CompilerPlugin {
}

const modifiedTestCases = new Set();
const modifiedHookFunctions = new Set();
testSuite.addDataFunctions(event.editor as any);
for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
for (const hookName of [group.setupFunctionName, group.tearDownFunctionName, group.beforeEachFunctionName, group.afterEachFunctionName]) {
if (hookName) {
const hookKey = group.testSuite.generatedNodeName + group.file.pkgPath + hookName.toLowerCase();
if (!modifiedHookFunctions.has(hookKey)) {
group.modifyAssertionsForHook(hookName, noEarlyExit, event.editor as any, this.session.namespaceLookup, scope);
modifiedHookFunctions.add(hookKey);
}
}
}
for (let testCase of [...group.testCases].filter((tc) => tc.isIncluded)) {
let caseName = group.testSuite.generatedNodeName + group.file.pkgPath + testCase.funcName;
if (!modifiedTestCases.has(caseName)) {
Expand Down
Loading
Loading