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

rokucommunity / rooibos / 26647638185

29 May 2026 03:57PM UTC coverage: 83.294% (-0.9%) from 84.234%
26647638185

Pull #342

github

web-flow
Merge 52bc7b13d into 9b649ad9c
Pull Request #342: v6

686 of 906 branches covered (75.72%)

Branch coverage included in aggregate %.

175 of 227 new or added lines in 14 files covered. (77.09%)

3 existing lines in 2 files now uncovered.

1099 of 1237 relevant lines covered (88.84%)

223.72 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
    BeforeProvideProgramEvent,
10
    AfterRemoveFileEvent,
11
    AfterProvideProgramEvent,
12
    AfterValidateProgramEvent
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';
96✔
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
    beforeProvideProgram(event: BeforeProvideProgramEvent): void {
41
        const builder = event.builder;
96✔
42
        this._builder = builder;
96✔
43

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

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

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

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

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

124
        return config;
96✔
125
    }
126

127
    afterProvideProgram(event: AfterProvideProgramEvent) {
128
        this.fileFactory.addFrameworkFiles(event.program);
120✔
129
    }
130

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

140
    afterProvideFile(event: AfterProvideFileEvent): void {
141
        for (const file of event.files) {
3,217✔
142

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

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

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

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

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

199
                }
200
            }
201
        }
202

203
        if (isBrsFile(event.file)) {
1,815✔
204
            if (this.shouldAddCodeCoverageToFile(event.file)) {
1,544✔
205
                this.codeCoverageProcessor.addCodeCoverage(event.file, event.editor);
8✔
206
            }
207
            if (this.shouldEnableGlobalMocksOnFile(event.file)) {
1,544✔
208
                this.mockUtil.enableGlobalMethodMocks(event.file, event.editor);
107✔
209
            }
210
        }
211
    }
212

213
    afterBuildProgram(event: BeforeBuildProgramEvent) {
214
        this.session.addLaunchHookFileIfNotPresent();
67✔
215
        this.codeCoverageProcessor.generateMetadata(this.config.isRecordingCodeCoverage, event.program);
67✔
216
    }
217

218
    afterValidateProgram(event: AfterValidateProgramEvent) {
219
        this.session.updateSessionStats();
86✔
220
        for (let testSuite of [...this.session.sessionInfo.testSuites.values()]) {
86✔
221
            testSuite.validate();
60✔
222
        }
223
        for (let file of this.fileFactory.addedFrameworkFiles) {
86✔
224
            // eslint-disable-next-line @typescript-eslint/dot-notation
225
            // file['diagnostics'] = [];
226
            event.program.diagnostics.clearForFile(file.srcPath);
2,236✔
227
        }
228
    }
229

230
    shouldSearchInFileForTests(file: BscFile) {
231
        if (!this.config.includeFilters || this.config.includeFilters.length === 0) {
3,221!
232
            return true;
×
233
        } else {
234
            for (let filter of this.config.includeFilters) {
3,221✔
235
                if (!minimatch(file.srcPath, filter, { dot: true })) {
3,345✔
236
                    return false;
3,159✔
237
                }
238
            }
239
        }
240
        return true;
62✔
241
    }
242
    shouldAddCodeCoverageToFile(file: BscFile) {
243
        if (!isBrsFile(file) || !this.config.isRecordingCodeCoverage) {
1,544✔
244
            return false;
1,337✔
245
        } else if (!this.config.coverageExcludedFiles) {
207!
246
            return true;
×
247
        } else {
248
            for (let filter of this.config.coverageExcludedFiles) {
207✔
249
                if (minimatch(file.destPath, filter, { dot: true })) {
1,021✔
250
                    return false;
199✔
251
                }
252
            }
253
        }
254
        return true;
8✔
255
    }
256

257
    shouldEnableGlobalMocksOnFile(file: BscFile) {
258
        if (!isBrsFile(file) || !this.config.isGlobalMethodMockingEnabled) {
1,544✔
259
            return false;
456✔
260
        } else if (!this.config.globalMethodMockingExcludedFiles) {
1,088!
261
            return true;
×
262
        } else {
263
            for (let filter of this.config.globalMethodMockingExcludedFiles) {
1,088✔
264
                if (minimatch(file.destPath, filter, { dot: true })) {
3,159✔
265
                    // console.log('±±±skipping file', file.pkgPath);
266
                    return false;
981✔
267
                }
268
            }
269
        }
270
        return true;
107✔
271
    }
272

273
    private shouldSkipFile(file: BscFile) {
274
        return file.pkgPath.toLowerCase().includes(standardizePath('source/bslib.brs'));
5,103✔
275
    }
276
}
277

278
export default () => {
1✔
279
    return new RooibosPlugin();
×
280
};
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