• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / rooibos / #746

20 Jan 2026 01:36PM UTC coverage: 84.0% (-0.08%) from 84.08%
#746

push

web-flow
Merge 7b51b1d63 into 0dd913602

665 of 876 branches covered (75.91%)

Branch coverage included in aggregate %.

5 of 7 new or added lines in 3 files covered. (71.43%)

104 existing lines in 12 files now uncovered.

1120 of 1249 relevant lines covered (89.67%)

222.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.85
/src/plugin.ts
1
import type {
2
    BscFile,
3
    CompilerPlugin,
4
    ProgramBuilder,
5
    XmlFile,
6
    OnPrepareFileEvent,
7
    BeforeBuildProgramEvent,
8
    AfterProvideFileEvent,
9
    BeforeProgramCreateEvent,
10
    AfterFileRemoveEvent,
11
    AfterProgramCreateEvent,
12
    AfterProgramValidateEvent
13
} from 'brighterscript';
14
import {
1✔
15
    isBrsFile,
16
    isXmlFile,
17
    util,
18
    standardizePath
19
} from 'brighterscript';
20
import { RooibosSession } from './lib/rooibos/RooibosSession';
1✔
21
import { CodeCoverageProcessor } from './lib/rooibos/CodeCoverageProcessor';
1✔
22
import { FileFactory } from './lib/rooibos/FileFactory';
1✔
23
import type { RooibosConfig } from './lib/rooibos/RooibosConfig';
24
import * as minimatch from 'minimatch';
1✔
25
import * as path from 'path';
1✔
26
import { MockUtil } from './lib/rooibos/MockUtil';
1✔
27
import { getScopeForSuite } from './lib/rooibos/Utils';
1✔
28
import { RooibosLogPrefix } from './lib/utils/Diagnostics';
1✔
29

30
export class RooibosPlugin implements CompilerPlugin {
1✔
31

32
    public name = 'rooibosPlugin';
91✔
33
    public session: RooibosSession;
34
    public codeCoverageProcessor: CodeCoverageProcessor;
35
    public mockUtil: MockUtil;
36
    public fileFactory: FileFactory;
37
    public _builder: ProgramBuilder;
38
    public config: RooibosConfig;
39

40
    beforeProgramCreate(event: BeforeProgramCreateEvent): void {
41
        const builder = event.builder;
91✔
42
        this._builder = builder;
91✔
43

44
        this.config = this.getConfig((builder.options as any).rooibos || {});
91✔
45

46
        this.fileFactory = new FileFactory();
91✔
47
        if (!this.session) {
91!
48
            this.session = new RooibosSession(builder, this.fileFactory);
91✔
49
            this.codeCoverageProcessor = new CodeCoverageProcessor(builder, this.fileFactory);
91✔
50
            this.mockUtil = new MockUtil(builder, this.fileFactory, this.session);
91✔
51
        }
52
    }
53
    private getConfig(options: any) {
54
        let config: RooibosConfig = options;
91✔
55
        if (config.printTestTimes === undefined) {
91!
56
            config.printTestTimes = true;
91✔
57
        }
58
        if (config.catchCrashes === undefined) {
91!
59
            config.catchCrashes = true;
91✔
60
        }
61
        if (config.colorizeOutput === undefined) {
91!
62
            config.colorizeOutput = false;
91✔
63
        }
64
        if (config.throwOnFailedAssertion === undefined) {
91!
65
            config.throwOnFailedAssertion = false;
91✔
66
        }
67
        if (config.sendHomeOnFinish === undefined) {
91!
68
            config.sendHomeOnFinish = true;
91✔
69
        }
70
        if (config.failFast === undefined) {
91!
71
            config.failFast = true;
91✔
72
        }
73
        if (config.showOnlyFailures === undefined) {
91!
74
            config.showOnlyFailures = true;
91✔
75
        }
76
        if (config.isRecordingCodeCoverage === undefined) {
91✔
77
            config.isRecordingCodeCoverage = false;
85✔
78
        }
79
        if (config.isGlobalMethodMockingEnabled === undefined) {
91✔
80
            config.isGlobalMethodMockingEnabled = false;
17✔
81
        }
82
        if (config.isGlobalMethodMockingEfficientMode === undefined) {
91✔
83
            config.isGlobalMethodMockingEfficientMode = true;
81✔
84
        }
85
        if (config.keepAppOpen === undefined) {
91!
86
            config.keepAppOpen = true;
91✔
87
        }
88
        if (config.testSceneName === undefined) {
91✔
89
            config.testSceneName = 'RooibosScene';
90✔
90
        }
91
        //ignore roku modules by default
92
        if (config.includeFilters === undefined) {
91!
93
            config.includeFilters = [
91✔
94
                '**/*.spec.bs',
95
                '!**/BaseTestSuite.spec.bs',
96
                '!**/roku_modules/**/*'];
97
        }
98

99
        const defaultCoverageExcluded = [
91✔
100
            '**/*.spec.bs',
101
            '**/roku_modules/**/*',
102
            '**/source/main.bs',
103
            '**/source/rooibos/**/*'
104
        ];
105

106
        // Set default coverage exclusions, or merge with defaults if available.
107
        if (config.coverageExcludedFiles === undefined) {
91✔
108
            config.coverageExcludedFiles = defaultCoverageExcluded;
85✔
109
        } else {
110
            config.coverageExcludedFiles.push(...defaultCoverageExcluded);
6✔
111
        }
112

113
        const defaultGlobalMethodMockingExcluded = [
91✔
114
            '**/*.spec.bs',
115
            '**/source/main.bs',
116
            '**/source/rooibos/**/*'
117
        ];
118
        if (config.globalMethodMockingExcludedFiles === undefined) {
91✔
119
            config.globalMethodMockingExcludedFiles = defaultGlobalMethodMockingExcluded;
81✔
120
        }
121

122
        return config;
91✔
123
    }
124

125
    afterProgramCreate(event: AfterProgramCreateEvent) {
126
        this.fileFactory.addFrameworkFiles(event.program);
112✔
127
    }
128

129
    afterFileRemove(event: AfterFileRemoveEvent) {
130
        // eslint-disable-next-line @typescript-eslint/dot-notation
131
        const xmlFile = event.file['rooibosXmlFile'] as XmlFile;
505✔
132
        if (xmlFile) {
505!
133
            // Remove the old generated xml files
134
            this._builder.program.removeFile(xmlFile.srcPath);
×
135
        }
136
    }
137

138
    afterProvideFile(event: AfterProvideFileEvent): void {
139
        for (const file of event.files) {
2,779✔
140

141
            if (!(isBrsFile(file) || isXmlFile(file)) || this.shouldSkipFile(file)) {
2,783!
UNCOV
142
                continue;
×
143
            }
144
            if (util.pathToUri(file.srcPath).includes('/rooibos/bsc-plugin/dist/framework')) {
2,783!
145
                // eslint-disable-next-line @typescript-eslint/dot-notation
UNCOV
146
                return;
×
147
            }
148
            if (this.fileFactory.isIgnoredFile(file) || !this.shouldSearchInFileForTests(file)) {
2,783✔
149
                return;
2,723✔
150
            }
151
            event.program.logger.log(RooibosLogPrefix, 'Processing test file', file.pkgPath);
60✔
152

153
            if (isBrsFile(file)) {
60!
154
                // Add the node test component so brighter script can validate the test files
155
                let suites = this.session.processFile(file);
60✔
156
                let nodeSuites = suites.filter((ts) => ts.isNodeTest);
67✔
157
                for (const suite of nodeSuites) {
60✔
158
                    const xmlFile = this._builder.program.setFile({
4✔
159
                        src: path.resolve(suite.xmlPkgPath),
160
                        dest: suite.xmlPkgPath
161
                    }, this.session.getNodeTestXmlText(suite));
162
                    // eslint-disable-next-line @typescript-eslint/dot-notation
163
                    file['rooibosXmlFile'] = xmlFile;
4✔
164
                    event.files.push(xmlFile);
4✔
165
                }
166
            }
167
        }
168
    }
169

170
    beforeBuildProgram(event: BeforeBuildProgramEvent) {
171
        const createdFiles = this.session.prepareForTranspile(event.editor, event.program, this.mockUtil);
62✔
172
        event.files.push(...createdFiles);
62✔
173
    }
174

175
    prepareFile(event: OnPrepareFileEvent) {
176
        if (this.shouldSkipFile(event.file)) {
1,617✔
177
            return;
62✔
178
        }
179
        let testSuite = this.session.sessionInfo.testSuitesToRun.find((ts) => ts.file.pkgPath === event.file.pkgPath);
1,555✔
180
        if (testSuite) {
1,555✔
181
            const scope = getScopeForSuite(testSuite);
37✔
182
            let noEarlyExit = testSuite.annotation.noEarlyExit;
37✔
183
            if (noEarlyExit) {
37!
UNCOV
184
                event.program.logger.warn(RooibosLogPrefix, `TestSuite "${testSuite.name}" is marked as noEarlyExit`);
×
185
            }
186

187
            const modifiedTestCases = new Set();
37✔
188
            testSuite.addDataFunctions(event.editor as any);
37✔
189
            for (let group of [...testSuite.testGroups.values()].filter((tg) => tg.isIncluded)) {
37✔
190
                for (let testCase of [...group.testCases].filter((tc) => tc.isIncluded)) {
43✔
191
                    let caseName = group.testSuite.generatedNodeName + group.file.pkgPath + testCase.funcName;
43✔
192
                    if (!modifiedTestCases.has(caseName)) {
43✔
193
                        group.modifyAssertions(testCase, noEarlyExit, event.editor as any, this.session.namespaceLookup, scope);
39✔
194
                        modifiedTestCases.add(caseName);
39✔
195
                    }
196

197
                }
198
            }
199
        }
200

201
        if (isBrsFile(event.file)) {
1,555✔
202
            if (this.shouldAddCodeCoverageToFile(event.file)) {
1,366✔
203
                this.codeCoverageProcessor.addCodeCoverage(event.file, event.editor);
11✔
204
            }
205
            if (this.shouldEnableGlobalMocksOnFile(event.file)) {
1,366✔
206
                this.mockUtil.enableGlobalMethodMocks(event.file, event.editor);
328✔
207
            }
208
        }
209
    }
210

211
    afterBuildProgram(event: BeforeBuildProgramEvent) {
212
        this.session.addLaunchHookFileIfNotPresent();
62✔
213
        this.codeCoverageProcessor.generateMetadata(this.config.isRecordingCodeCoverage, event.program);
62✔
214
    }
215

216
    afterProgramValidate(event: AfterProgramValidateEvent) {
217
        this.session.updateSessionStats();
81✔
218
        for (let testSuite of [...this.session.sessionInfo.testSuites.values()]) {
81✔
219
            testSuite.validate();
58✔
220
        }
221
        for (let file of this.fileFactory.addedFrameworkFiles) {
81✔
222
            // eslint-disable-next-line @typescript-eslint/dot-notation
223
            // file['diagnostics'] = [];
224
            event.program.diagnostics.clearForFile(file.srcPath);
1,944✔
225
        }
226
    }
227

228
    shouldSearchInFileForTests(file: BscFile) {
229
        if (!this.config.includeFilters || this.config.includeFilters.length === 0) {
2,783!
UNCOV
230
            return true;
×
231
        } else {
232
            for (let filter of this.config.includeFilters) {
2,783✔
233
                if (!minimatch(file.srcPath, filter, { dot: true })) {
2,903✔
234
                    return false;
2,723✔
235
                }
236
            }
237
        }
238
        return true;
60✔
239
    }
240
    shouldAddCodeCoverageToFile(file: BscFile) {
241
        if (!isBrsFile(file) || !this.config.isRecordingCodeCoverage) {
1,366✔
242
            return false;
1,234✔
243
        } else if (!this.config.coverageExcludedFiles) {
132!
UNCOV
244
            return true;
×
245
        } else {
246
            for (let filter of this.config.coverageExcludedFiles) {
132✔
247
                if (minimatch(file.destPath, filter, { dot: true })) {
632✔
248
                    return false;
121✔
249
                }
250
            }
251
        }
252
        return true;
11✔
253
    }
254

255
    shouldEnableGlobalMocksOnFile(file: BscFile) {
256
        if (!isBrsFile(file) || !this.config.isGlobalMethodMockingEnabled) {
1,366✔
257
            return false;
370✔
258
        } else if (!this.config.globalMethodMockingExcludedFiles) {
996!
UNCOV
259
            return true;
×
260
        } else {
261
            for (let filter of this.config.globalMethodMockingExcludedFiles) {
996✔
262
                if (minimatch(file.destPath, filter, { dot: true })) {
2,476✔
263
                    // console.log('±±±skipping file', file.pkgPath);
264
                    return false;
668✔
265
                }
266
            }
267
        }
268
        return true;
328✔
269
    }
270

271
    private shouldSkipFile(file: BscFile) {
272
        return file.pkgPath.toLowerCase().includes(standardizePath('source/bslib.brs'));
4,400✔
273
    }
274
}
275

276
export default () => {
1✔
UNCOV
277
    return new RooibosPlugin();
×
278
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc