Skip to content
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,29 @@ This is a shortcut for `npm test -- --testFileName=MyTestableUnit`
| Flag | Description |
|------|-------------|
| `--testFileName=<name>` | Run only tests whose file name matches `<name>` |
| `--tests=<patterns>` | Run only tests matching semicolon-separated glob patterns, filtered at build time (see below) |
| `--forceConnect` | Before deploying, kill any local process connected to port 8085 and proceed (see below) |

### Filtering tests with `--tests`

`--tests` accepts semicolon-separated glob patterns (supports `*` and `?` wildcards) matched case-insensitively against unit names (i.e. the filename without `.test.brs`). Only matching test files are compiled and deployed — making it significantly faster than running the full suite.

```shell
# Run all tests whose name starts with "Home"
npm test -- --tests="Home*"

# Run tests for multiple groups in a single build+deploy
npm test -- --tests="Home*;Video*;Button"

# Exact names (no wildcards needed)
npm test -- --tests="SomeService;Button"

# Via environment variable
TESTS="Home*;Video*" npm test
```

Use `--testFileName` instead when you want to filter at the Roku device runtime level (all files still get deployed). Use `--tests` when you want to reduce build and deploy time by excluding unrelated tests entirely.

### Port 8085 and debug session behaviour

Roku exposes a debug console on **port 8085**. Only one client can be connected at a time.
Expand Down
14 changes: 14 additions & 0 deletions packager-steps/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,18 @@ if (args.env === 'test' && ((scriptName === 'test' && args._.length) || args._.l
args.testFileName = args._.slice(-1)[0];
}

/**
* @type {string} Semicolon-separated list of glob patterns to filter which test files are built and run.
* Supports * and ? wildcards. Matched against the unit name (filename without .test.brs).
* Takes precedence over --testFileName when provided.
*
* npm test -- --tests=Rail*;Video*;Tile*
* npm test -- --tests=RailsService;Tile
* TESTS=Rail*;Video* npm test
*/

if (!args.tests && process.env.TESTS) {
args.tests = process.env.TESTS;
}

module.exports = args;
24 changes: 23 additions & 1 deletion plugins/generate-tests/prepare-test-schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@ const fs = require('fs').promises;
const { glob } = require('glob');
const { randomUUID } = require('crypto');

const args = require('../../../packager-steps/args');
const TestSceneGenerator = require('./helpers/test-scene-generator');
const TestMainFileContentGenerator = require('./helpers/test-main-file-content-generator');
const TestSchema = require('./helpers/test-schema');
const TestXmlGenerator = require('./helpers/test-xml-generator');

const IGNORED_TEST_FILES_PATH_PATTERN = '/components/**/_tests/**/*_*.brs';
const MAIN_FILE_LOCATION = '/source/Main.brs';
const TEST_FILE_NAME_REGEX = /[^/]+(?=\.test\.brs)/;
const TEST_FILES_PATH_PATTERN = '/components/**/_tests/**/*.test.brs';
const TESTS_LOCATION = '/components/tests/auto-generated/';

module.exports = async function prepareTestSchema(dir) {
const testFilePaths = await glob(`${dir}${TEST_FILES_PATH_PATTERN}`, {
let testFilePaths = await glob(`${dir}${TEST_FILES_PATH_PATTERN}`, {
ignore: `${dir}${IGNORED_TEST_FILES_PATH_PATTERN}`,
});

if (args.tests) {
const patterns = args.tests.split(';').map((pattern) => pattern.trim()).filter(Boolean);
testFilePaths = testFilePaths.filter((filePath) => {
const unitName = filePath.match(TEST_FILE_NAME_REGEX)?.[0];

return unitName && patterns.some((pattern) => _matchesPattern(unitName, pattern));
});
console.log(`[generate-tests] --tests filter: ${patterns.join(', ')} → ${testFilePaths.length} test file(s)`);
}

const testXmlGenerator = new TestXmlGenerator(dir);

await TestSceneGenerator.generate(`${dir}${TESTS_LOCATION}`);
Expand All @@ -25,6 +38,15 @@ module.exports = async function prepareTestSchema(dir) {
);
}

function _matchesPattern(str, pattern) {
const regex = new RegExp(
'^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$',
'i',
);

return regex.test(str);
}

async function prepareSchema(testFilePath, testXmlGenerator, rootDir) {
const testSchema = await TestSchema.load(testFilePath);
if (!testSchema) {
Expand Down