From b08a10c32b20e2f6c4498d8855c97d44d1845075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Che=C5=82kowski?= Date: Wed, 1 Apr 2026 20:40:18 +0200 Subject: [PATCH] feat: add --tests flag for multi-pattern build-time test filtering Adds a new --tests CLI flag (and TESTS env var) that accepts semicolon-separated glob patterns matched against unit names (filename without .test.brs). Only matching test files are compiled and deployed, reducing build and deploy time when working on a subset of tests. Examples: npm test -- --tests="Home*;Video*;Button" TESTS="Home*" npm test Unlike --testFileName (runtime filter on device), --tests filters at build time before test schemas are generated. --- README.md | 21 ++++++++++++++++ packager-steps/args.js | 14 +++++++++++ .../prepare-test-schema/index.js | 24 ++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e41deea..e7a3a13 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,29 @@ This is a shortcut for `npm test -- --testFileName=MyTestableUnit` | Flag | Description | |------|-------------| | `--testFileName=` | Run only tests whose file name matches `` | +| `--tests=` | 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. diff --git a/packager-steps/args.js b/packager-steps/args.js index b115883..0f14292 100644 --- a/packager-steps/args.js +++ b/packager-steps/args.js @@ -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; diff --git a/plugins/generate-tests/prepare-test-schema/index.js b/plugins/generate-tests/prepare-test-schema/index.js index 5c2c6c0..f8b8e22 100644 --- a/plugins/generate-tests/prepare-test-schema/index.js +++ b/plugins/generate-tests/prepare-test-schema/index.js @@ -2,6 +2,7 @@ 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'); @@ -9,13 +10,25 @@ 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}`); @@ -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) {