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
11 changes: 9 additions & 2 deletions .github/workflows/npm_release.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: NPM Release
on:
push:
branches:
Expand All @@ -19,7 +20,7 @@ permissions:
jobs:
build:
name: Build
runs-on: macos-15-intel
runs-on: macos-15
outputs:
npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }}
npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }}
Expand Down Expand Up @@ -90,7 +91,7 @@ jobs:
with:
name: debug-symbols
path: test-app/runtime/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib/*

test:
name: Test
runs-on: macos-15-intel
Expand Down Expand Up @@ -143,6 +144,12 @@ jobs:
#target: google_apis
arch: ${{env.ANDROID_ABI}}
script: ./gradlew runtestsAndVerifyResults --stacktrace
- name: Upload Test Results
if: ${{ !cancelled() }} # run this step even if previous step failed
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: android-unit-test-results
path: test-app/dist/android_unit_test_results.xml
publish:
runs-on: ubuntu-latest
environment: npm-publish
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
name: Pull Request
on:
pull_request:

Expand All @@ -9,14 +10,13 @@ env:
ANDROID_ABI: x86_64
NDK_ARCH: darwin


permissions:
contents: read

jobs:
build:
name: Build
runs-on: macos-15-intel
runs-on: macos-15
outputs:
npm_version: ${{ steps.npm_version_output.outputs.NPM_VERSION }}
npm_tag: ${{ steps.npm_version_output.outputs.NPM_TAG }}
Expand Down Expand Up @@ -128,4 +128,10 @@ jobs:
# this is needed on API 30+
#target: google_apis
arch: ${{env.ANDROID_ABI}}
script: ./gradlew runtestsAndVerifyResults --stacktrace
script: ./gradlew runtestsAndVerifyResults --stacktrace
- name: Upload Test Results
if: ${{ !cancelled() }} # run this step even if previous step failed
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: android-unit-test-results
path: test-app/dist/android_unit_test_results.xml
20 changes: 20 additions & 0 deletions .github/workflows/test_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Test Report"
on:
workflow_run:
workflows: ["Pull Request", "NPM Release"] # runs after Pull Request workflow
types:
- completed
permissions:
contents: read
actions: read
checks: write
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: dorny/test-reporter@b082adf0eced0765477756c2a610396589b8c637 # v2.5.0
with:
name: Android Runtime Tests
artifact: android-unit-test-results # artifact name
path: test-app/dist/android_unit_test_results.xml
reporter: jest-junit # Format of test results
Original file line number Diff line number Diff line change
Expand Up @@ -215,50 +215,7 @@
return;
} catch (f) { errors.push(' NodeJS attempt: ' + f.message); }
try {
// Instead of writing XML files, output test summary to console
// Parse the XML text to extract test summary
var testMatch = text.match(/tests="(\d+)"/g);
var failureMatch = text.match(/failures="(\d+)"/g);
var errorMatch = text.match(/errors="(\d+)"/g);
var skippedMatch = text.match(/skipped="(\d+)"/g);

var totalTests = 0;
var totalFailures = 0;
var totalErrors = 0;
var totalSkipped = 0;

// Sum up all test suite results
if (testMatch) {
for (var i = 0; i < testMatch.length; i++) {
var match = testMatch[i].match(/tests="(\d+)"/);
if (match) totalTests += parseInt(match[1]);
}
}

if (failureMatch) {
for (var i = 0; i < failureMatch.length; i++) {
var match = failureMatch[i].match(/failures="(\d+)"/);
if (match) totalFailures += parseInt(match[1]);
}
}

if (errorMatch) {
for (var i = 0; i < errorMatch.length; i++) {
var match = errorMatch[i].match(/errors="(\d+)"/);
if (match) totalErrors += parseInt(match[1]);
}
}

if (skippedMatch) {
for (var i = 0; i < skippedMatch.length; i++) {
var match = skippedMatch[i].match(/skipped="(\d+)"/);
if (match) totalSkipped += parseInt(match[1]);
}
}

// Output in a format our test checker can detect
var resultPrefix = (totalFailures > 0 || totalErrors > 0) ? "FAILURE:" : "SUCCESS:";
console.log(resultPrefix + " " + totalTests + " specs, " + (totalFailures + totalErrors) + " failures, " + totalSkipped + " skipped");
__JUnitSaveResults(text);

return;
} catch (f) {
Expand Down
15 changes: 15 additions & 0 deletions test-app/app/src/main/assets/app/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ global.__onUncaughtError = function(error){
}

require('./Infrastructure/timers');
global.__JUnitSaveResults = function (unitTestResults) {
var pathToApp = '/data/data/com.tns.testapplication';
var unitTestFileName = 'android_unit_test_results.xml';
try {
var javaFile = new java.io.File(pathToApp, unitTestFileName);
var stream = new java.io.FileOutputStream(javaFile);
var actualEncoding = 'UTF-8';
var writer = new java.io.OutputStreamWriter(stream, actualEncoding);
writer.write(unitTestResults);
writer.close();
}
catch (exception) {
android.util.Log.d("TEST RESULTS", 'failed writing to files dir: ' + exception)
}
};

require('./Infrastructure/Jasmine/jasmine-2.0.1/boot'); //runs jasmine, attaches the junitOutputter

Expand Down
2 changes: 0 additions & 2 deletions test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,4 @@ require('./tests/testURLSearchParamsImpl.js');
require('./tests/testPerformanceNow');
require('./tests/testQueueMicrotask');

// ES MODULE TESTS
__log("=== Running ES Modules Tests ===");
require("./tests/testESModules.mjs");
199 changes: 33 additions & 166 deletions test-app/app/src/main/assets/app/tests/testESModules.mjs
Original file line number Diff line number Diff line change
@@ -1,167 +1,34 @@
async function runESModuleTests() {
let passed = 0;
let failed = 0;
const failureDetails = [];

const recordPass = (message, ...args) => {
console.log(`✅ PASS: ${message}`, ...args);
passed++;
};

const recordFailure = (message, options = {}) => {
const { error, details = [] } = options;
const fullMessage = error?.message
? `${message}: ${error.message}`
: message;
console.log(`❌ FAIL: ${fullMessage}`);
details.forEach((detail) => console.log(detail));
if (error?.stack) {
console.log("Stack trace:", error.stack);
}
failed++;
failureDetails.push(fullMessage);
};

const logFinalSummary = () => {
console.log("\n=== ES MODULE TEST RESULTS ===");
console.log("Tests passed:", passed);
console.log("Tests failed:", failed);
console.log("Total tests:", passed + failed);

if (failed === 0) {
console.log("ALL ES MODULE TESTS PASSED!");
} else {
console.log("SOME ES MODULE TESTS FAILED!");
console.log("FAILURE DETECTED: Starting failure logging");
failureDetails.forEach((detail) => {
console.log(` ❌ ${detail}`);
});
}
};

try {
// Test 1: Load .mjs files as ES modules
console.log("\n--- Test 1: Loading .mjs files as ES modules ---");
try {
const moduleExports = await import("~/testSimpleESModule.mjs");
if (moduleExports) {
recordPass("Module exports:", JSON.stringify(moduleExports));
} else {
recordFailure("ES Module loaded but exports are null");
}

if (moduleExports?.moduleType === "ES Module") {
recordPass("moduleType check passed");
} else {
recordFailure("moduleType check failed");
}
} catch (e) {
recordFailure("Error loading ES module", { error: e });
}

// Test 2: Test import.meta functionality
console.log("\n--- Test 2: Testing import.meta functionality ---");
try {
const importMetaModule = await import("~/testImportMeta.mjs");
if (
importMetaModule &&
importMetaModule.default &&
typeof importMetaModule.default === "function"
) {
const metaResults = importMetaModule.default();
console.log(
"import.meta test results:",
JSON.stringify(metaResults, null, 2)
);

if (
metaResults &&
metaResults.hasImportMeta &&
metaResults.hasUrl &&
metaResults.hasDirname
) {
recordPass("import.meta properties present");
console.log(" - import.meta.url:", metaResults.url);
console.log(" - import.meta.dirname:", metaResults.dirname);
} else {
recordFailure("import.meta properties missing", {
details: [
` - hasImportMeta: ${metaResults?.hasImportMeta}`,
` - hasUrl: ${metaResults?.hasUrl}`,
` - hasDirname: ${metaResults?.hasDirname}`,
],
});
}
} else {
recordFailure("import.meta module has no default export function");
}
} catch (e) {
recordFailure("Error testing import.meta", { error: e });
}

// Test 3: Test Worker enhancements
console.log("\n--- Test 3: Testing Worker enhancements ---");
try {
const workerModule = await import("~/testWorkerFeatures.mjs");
if (
workerModule &&
workerModule.testWorkerFeatures &&
typeof workerModule.testWorkerFeatures === "function"
) {
const workerResults = workerModule.testWorkerFeatures();
console.log(
"Worker features test results:",
JSON.stringify(workerResults, null, 2)
);

if (
workerResults &&
workerResults.stringPathSupported &&
workerResults.urlObjectSupported &&
workerResults.tildePathSupported
) {
recordPass("Worker enhancement features present");
console.log(
" - String path support:",
workerResults.stringPathSupported
);
console.log(
" - URL object support:",
workerResults.urlObjectSupported
);
console.log(
" - Tilde path support:",
workerResults.tildePathSupported
);
} else {
recordFailure("Worker enhancement features missing", {
details: [
` - stringPathSupported: ${workerResults?.stringPathSupported}`,
` - urlObjectSupported: ${workerResults?.urlObjectSupported}`,
` - tildePathSupported: ${workerResults?.tildePathSupported}`,
],
});
}
} else {
recordFailure(
"Worker features module has no testWorkerFeatures function"
);
}
} catch (e) {
recordFailure("Error testing Worker features", { error: e });
}
} catch (unexpectedError) {
recordFailure("Unexpected ES module test harness failure", {
error: unexpectedError,
});
} finally {
logFinalSummary();
}

return { passed, failed };
}

// Run the tests immediately (avoid top-level await for broader runtime support)
runESModuleTests().catch((e) => {
console.error("ES Module top-level failure:", e?.message ?? e);
describe("ES Modules", () => {
it("loads .mjs files as ES modules", async () => {
const moduleExports = await import("~/testSimpleESModule.mjs");
expect(moduleExports).toBeTruthy();
expect(moduleExports?.moduleType).toBe("ES Module");
});

it("supports import.meta functionality", async () => {
const importMetaModule = await import("~/testImportMeta.mjs");
expect(importMetaModule).toBeTruthy();
expect(typeof importMetaModule.default).toBe("function");

const metaResults = importMetaModule.default();
expect(metaResults).toBeTruthy();
expect(metaResults.hasImportMeta).toBe(true);
expect(metaResults.hasUrl).toBe(true);
expect(metaResults.hasDirname).toBe(true);
expect(metaResults.url).toBeTruthy();
expect(metaResults.dirname).toBeTruthy();
});

it("supports Worker enhancements", async () => {
// TODO: make these tests actually be normal tests instead of just importing and checking existence
const workerModule = await import("~/testWorkerFeatures.mjs");
expect(workerModule).toBeTruthy();
expect(typeof workerModule.testWorkerFeatures).toBe("function");

const workerResults = workerModule.testWorkerFeatures();
expect(workerResults).toBeTruthy();
expect(workerResults.stringPathSupported).toBe(true);
expect(workerResults.urlObjectSupported).toBe(true);
expect(workerResults.tildePathSupported).toBe(true);
});
});
Loading
Loading