From 7f1f234c436f052e2bcb3cad9c991d9f19a9f6a2 Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Sun, 29 Mar 2026 15:22:32 +0200 Subject: [PATCH 01/16] tests: update test-app to 3.4 --- test-app/.meteor/packages | 24 +++---- test-app/.meteor/release | 2 +- test-app/.meteor/versions | 134 ++++++++++++++++++-------------------- 3 files changed, 77 insertions(+), 83 deletions(-) diff --git a/test-app/.meteor/packages b/test-app/.meteor/packages index 3213e9618..2517db45f 100644 --- a/test-app/.meteor/packages +++ b/test-app/.meteor/packages @@ -4,16 +4,16 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base@1.5.2-rc300.2 # Packages every Meteor app needs to have -mobile-experience@1.1.2-rc300.2 # Packages for a great mobile UX -mongo@2.0.0-rc300.2 # The database Meteor supports right now -static-html@1.3.3-rc300.2 # Define static page content in .html files -reactive-var@1.0.13-rc300.2 # Reactive variable for tracker -tracker@1.3.4-rc300.2 # Meteor's client-side reactive programming library +meteor-base@1.5.2 # Packages every Meteor app needs to have +mobile-experience@1.1.2 # Packages for a great mobile UX +mongo@2.2.0 # The database Meteor supports right now +static-html@1.5.0 # Define static page content in .html files +reactive-var@1.0.13 # Reactive variable for tracker +tracker@1.3.4 # Meteor's client-side reactive programming library -standard-minifier-css@1.9.3-rc300.2 # CSS minifier run for production mode -standard-minifier-js@3.0.0-rc300.2 # JS minifier run for production mode -es5-shim@4.8.1-rc300.2 # ECMAScript 5 compatibility for older browsers -ecmascript@0.16.9-rc300.2 # Enable ECMAScript2015+ syntax in app code -typescript@5.4.3-rc300.2 # Enable TypeScript syntax in .ts and .tsx modules -shell-server@0.6.0-rc300.2 # Server-side component of the `meteor shell` command +standard-minifier-css@1.10.0 # CSS minifier run for production mode +standard-minifier-js@3.2.0 # JS minifier run for production mode +es5-shim@4.8.1 # ECMAScript 5 compatibility for older browsers +ecmascript@0.17.0 # Enable ECMAScript2015+ syntax in app code +typescript@5.9.3 # Enable TypeScript syntax in .ts and .tsx modules +shell-server@0.7.0 # Server-side component of the `meteor shell` command diff --git a/test-app/.meteor/release b/test-app/.meteor/release index b229c371c..703a72252 100644 --- a/test-app/.meteor/release +++ b/test-app/.meteor/release @@ -1 +1 @@ -METEOR@3.0-rc.2 +METEOR@3.4 diff --git a/test-app/.meteor/versions b/test-app/.meteor/versions index 589a1f1d2..cc45d72c1 100644 --- a/test-app/.meteor/versions +++ b/test-app/.meteor/versions @@ -1,70 +1,64 @@ -allow-deny@2.0.0-rc300.2 -autoupdate@2.0.0-rc300.2 -babel-compiler@7.11.0-rc300.2 -babel-runtime@1.5.2-rc300.2 -base64@1.0.13-rc300.2 -binary-heap@1.0.12-rc300.2 -blaze-tools@2.0.0-rc300.2 -boilerplate-generator@2.0.0-rc300.2 -caching-compiler@2.0.0-rc300.2 -caching-html-compiler@2.0.0-rc300.2 -callback-hook@1.6.0-rc300.2 -check@1.4.2-rc300.2 -core-runtime@1.0.0-rc300.2 -ddp@1.4.2-rc300.2 -ddp-client@3.0.0-rc300.2 -ddp-common@1.4.1-rc300.2 -ddp-server@3.0.0-rc300.2 -diff-sequence@1.1.3-rc300.2 -dynamic-import@0.7.4-rc300.2 -ecmascript@0.16.9-rc300.2 -ecmascript-runtime@0.8.2-rc300.2 -ecmascript-runtime-client@0.12.2-rc300.2 -ecmascript-runtime-server@0.11.1-rc300.2 -ejson@1.1.4-rc300.2 -es5-shim@4.8.1-rc300.2 -facts-base@1.0.2-rc300.2 -fetch@0.1.5-rc300.2 -geojson-utils@1.0.12-rc300.2 -hot-code-push@1.0.5-rc300.2 -html-tools@2.0.0-rc300.2 -htmljs@2.0.0-rc300.2 -id-map@1.2.0-rc300.2 -inter-process-messaging@0.1.2-rc300.2 -launch-screen@2.0.1-rc300.2 -logging@1.3.5-rc300.2 -meteor@2.0.0-rc300.2 -meteor-base@1.5.2-rc300.2 -minifier-css@2.0.0-rc300.2 -minifier-js@3.0.0-rc300.2 -minimongo@2.0.0-rc300.2 -mobile-experience@1.1.2-rc300.2 -mobile-status-bar@1.1.1-rc300.2 -modern-browsers@0.1.11-rc300.2 -modules@0.20.1-rc300.2 -modules-runtime@0.13.2-rc300.2 -mongo@2.0.0-rc300.2 -mongo-decimal@0.1.4-beta300.7 -mongo-dev-server@1.1.1-rc300.2 -mongo-id@1.0.9-rc300.2 -npm-mongo@4.16.2-rc300.2 -ordered-dict@1.2.0-rc300.2 -promise@1.0.0-rc300.2 -random@1.2.2-rc300.2 -react-fast-refresh@0.2.9-rc300.2 -reactive-var@1.0.13-rc300.2 -reload@1.3.2-rc300.2 -retry@1.1.1-rc300.2 -routepolicy@1.1.2-rc300.2 -shell-server@0.6.0-rc300.2 -socket-stream-client@0.5.3-rc300.2 -spacebars-compiler@2.0.0-rc300.2 -standard-minifier-css@1.9.3-rc300.2 -standard-minifier-js@3.0.0-rc300.2 -static-html@1.3.3-rc300.2 -templating-tools@2.0.0-rc300.2 -tracker@1.3.4-rc300.2 -typescript@5.4.3-rc300.2 -underscore@1.6.2-rc300.2 -webapp@2.0.0-rc300.2 -webapp-hashing@1.1.2-rc300.2 +allow-deny@2.1.0 +autoupdate@2.0.1 +babel-compiler@7.13.0 +babel-runtime@1.5.2 +base64@1.0.13 +binary-heap@1.0.12 +boilerplate-generator@2.1.0 +caching-compiler@2.0.1 +callback-hook@1.6.1 +check@1.5.0 +core-runtime@1.0.0 +ddp@1.4.2 +ddp-client@3.1.1 +ddp-common@1.4.4 +ddp-server@3.1.2 +diff-sequence@1.1.3 +dynamic-import@0.7.4 +ecmascript@0.17.0 +ecmascript-runtime@0.8.3 +ecmascript-runtime-client@0.12.3 +ecmascript-runtime-server@0.11.1 +ejson@1.1.5 +es5-shim@4.8.1 +facts-base@1.0.2 +fetch@0.1.6 +geojson-utils@1.0.12 +hot-code-push@1.0.5 +id-map@1.2.0 +inter-process-messaging@0.1.2 +launch-screen@2.0.1 +logging@1.3.6 +meteor@2.2.0 +meteor-base@1.5.2 +minifier-css@2.0.1 +minifier-js@3.1.0 +minimongo@2.0.5 +mobile-experience@1.1.2 +mobile-status-bar@1.1.1 +modern-browsers@0.2.3 +modules@0.20.3 +modules-runtime@0.13.2 +mongo@2.2.0 +mongo-decimal@0.2.0 +mongo-dev-server@1.1.1 +mongo-id@1.0.9 +npm-mongo@6.16.1 +ordered-dict@1.2.0 +promise@1.0.0 +random@1.2.2 +react-fast-refresh@0.3.0 +reactive-var@1.0.13 +reload@1.3.2 +retry@1.1.1 +routepolicy@1.1.2 +shell-server@0.7.0 +socket-stream-client@0.6.1 +standard-minifier-css@1.10.0 +standard-minifier-js@3.2.0 +static-html@1.5.0 +static-html-tools@1.0.0 +tracker@1.3.4 +typescript@5.9.3 +webapp@2.1.0 +webapp-hashing@1.1.2 From 1cd9cac1fa7a4c802c458551f4ed42de1532fd3e Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Sun, 29 Mar 2026 15:22:49 +0200 Subject: [PATCH 02/16] tests: update npm deps to latest --- test-app/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test-app/package.json b/test-app/package.json index e392d5ae2..951576f4d 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -14,13 +14,13 @@ }, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.27.1", - "jquery": "^3.7.1", - "meteor-node-stubs": "^1.2.17", - "puppeteer": "^24.8.2" + "@babel/runtime": "^7.29.2", + "jquery": "^4.0.0", + "meteor-node-stubs": "^1.2.27", + "puppeteer": "^24.40.0" }, "devDependencies": { - "@quave/eslint-config-quave": "^3.0.0" + "@quave/eslint-config-quave": "^3.0.1" }, "eslintConfig": { "extends": [ From 3a9c7835ae263f3e7e42aee26cb12a745312256a Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Sun, 29 Mar 2026 15:24:45 +0200 Subject: [PATCH 03/16] fix(blaze): explicitly catch and report exceptions in then(maybePromises, fn) --- packages/blaze/materializer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blaze/materializer.js b/packages/blaze/materializer.js index 092de816b..77401783c 100644 --- a/packages/blaze/materializer.js +++ b/packages/blaze/materializer.js @@ -97,7 +97,7 @@ const isPromiseLike = x => !!x && typeof x.then === 'function'; function then(maybePromise, fn) { if (isPromiseLike(maybePromise)) { - maybePromise.then(fn, Blaze._reportException); + maybePromise.then(fn).catch(Blaze._reportException); } else { fn(maybePromise); } From ce83d375ea96a70adb8385bc0dfdd46058aec12a Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Mon, 30 Mar 2026 09:19:56 +0200 Subject: [PATCH 04/16] fix(tests): move async tests to Tinytest.addAsync --- .../observe_sequence_tests.js | 114 +++++++++--------- .../spacebars-tests/old_templates_tests.js | 6 +- packages/spacebars-tests/template_tests.js | 8 +- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/observe-sequence/observe_sequence_tests.js b/packages/observe-sequence/observe_sequence_tests.js index c40ffbe92..01c471922 100644 --- a/packages/observe-sequence/observe_sequence_tests.js +++ b/packages/observe-sequence/observe_sequence_tests.js @@ -145,23 +145,23 @@ function runInVM(code) { return res; } -Tinytest.add('observe-sequence - initial data for all sequence types', function (test) { - runOneObserveSequenceTestCase(test, function () { +Tinytest.addAsync('observe-sequence - initial data for all sequence types', async function (test) { + await runOneObserveSequenceTestCase(test, function () { return null; }, function () {}, []); - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return []; }, function () {}, []); - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return [{foo: 1}, {bar: 2}]; }, function () {}, [ {addedAt: [0, {foo: 1}, 0, null]}, {addedAt: [1, {bar: 2}, 1, null]} ]); - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return [{_id: "13", foo: 1}, {_id: "37", bar: 2}]; }, function () {}, [ {addedAt: ["13", {_id: "13", foo: 1}, 0, null]}, @@ -169,7 +169,7 @@ Tinytest.add('observe-sequence - initial data for all sequence types', function ]); // sub-classed arrays - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return new ArraySubclass({_id: "13", foo: 1}, {_id: "37", bar: 2}); }, function () {}, [ {addedAt: ["13", {_id: "13", foo: 1}, 0, null]}, @@ -177,14 +177,14 @@ Tinytest.add('observe-sequence - initial data for all sequence types', function ]); // Execute in VM - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return new runInVM('new Array({_id: "13", foo: 1}, {_id: "37", bar: 2})'); }, function () {}, [ {addedAt: ["13", {_id: "13", foo: 1}, 0, null]}, {addedAt: ["37", {_id: "37", bar: 2}, 1, null]} ]); - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { const coll = new Mongo.Collection(null); coll.insert({_id: "13", foo: 1}); coll.insert({_id: "37", bar: 2}); @@ -197,7 +197,7 @@ Tinytest.add('observe-sequence - initial data for all sequence types', function // shouldn't break on array with duplicate _id's, and the ids sent // in the callbacks should be distinct - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return [ {_id: "13", foo: 1}, {_id: "13", foo: 2} @@ -208,12 +208,12 @@ Tinytest.add('observe-sequence - initial data for all sequence types', function ], /*numExpectedWarnings = */1); // non-array iterable (empty) - if(typeof Map == 'function') runOneObserveSequenceTestCase(test, function () { + if(typeof Map == 'function') await runOneObserveSequenceTestCase(test, function () { return new Map(); }, function () {}, []); // non-array iterable (non-empty) - if(typeof Set == 'function') runOneObserveSequenceTestCase(test, function () { + if(typeof Set == 'function') await runOneObserveSequenceTestCase(test, function () { return new Set([{foo: 1}, {bar: 2}]); }, function () {}, [ {addedAt: [0, {foo: 1}, 0, null]}, @@ -221,11 +221,11 @@ Tinytest.add('observe-sequence - initial data for all sequence types', function ]); }); -Tinytest.add('observe-sequence - array to other array', function (test) { +Tinytest.addAsync('observe-sequence - array to other array', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -240,11 +240,11 @@ Tinytest.add('observe-sequence - array to other array', function (test) { ]); }); -Tinytest.add('observe-sequence - array to other array, strings', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, strings', async function (test) { const dep = new Tracker.Dependency; let seq = ["A", "B"]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -258,8 +258,8 @@ Tinytest.add('observe-sequence - array to other array, strings', function (test) ]); }); -Tinytest.add('observe-sequence - bug #7850 array with null values', function (test) { - runOneObserveSequenceTestCase(test, function () { +Tinytest.addAsync('observe-sequence - bug #7850 array with null values', async function (test) { + await runOneObserveSequenceTestCase(test, function () { return [1, null]; }, function () {}, [ {addedAt: [1, 1, 0, null]}, @@ -267,11 +267,11 @@ Tinytest.add('observe-sequence - bug #7850 array with null values', function (te ]); }); -Tinytest.add('observe-sequence - array to other array, objects without ids', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, objects without ids', async function (test) { const dep = new Tracker.Dependency; let seq = [{foo: 1}, {bar: 2}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -285,11 +285,11 @@ Tinytest.add('observe-sequence - array to other array, objects without ids', fun ]); }); -Tinytest.add('observe-sequence - array to other array, changes', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, changes', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}, {_id: "42", baz: 42}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -308,11 +308,11 @@ Tinytest.add('observe-sequence - array to other array, changes', function (test) ]); }); -Tinytest.add('observe-sequence - array to other array, movedTo', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, movedTo', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}, {_id: "42", baz: 42}, {_id: "43", baz: 43}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -334,11 +334,11 @@ Tinytest.add('observe-sequence - array to other array, movedTo', function (test) ]); }); -Tinytest.add('observe-sequence - array to other array, movedTo the end', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, movedTo the end', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -358,11 +358,11 @@ Tinytest.add('observe-sequence - array to other array, movedTo the end', functio ]); }); -Tinytest.add('observe-sequence - array to other array, movedTo later position but not the latest #2845', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, movedTo later position but not the latest #2845', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -383,11 +383,11 @@ Tinytest.add('observe-sequence - array to other array, movedTo later position bu ]); }); -Tinytest.add('observe-sequence - array to other array, movedTo earlier position but not the first', function (test) { +Tinytest.addAsync('observe-sequence - array to other array, movedTo earlier position but not the first', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "0"}, {_id: "1"}, {_id: "2"}, {_id: "3"}, {_id: "4"}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -410,11 +410,11 @@ Tinytest.add('observe-sequence - array to other array, movedTo earlier position ]); }); -Tinytest.add('observe-sequence - array to null', function (test) { +Tinytest.addAsync('observe-sequence - array to null', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -428,11 +428,11 @@ Tinytest.add('observe-sequence - array to null', function (test) { ]); }); -Tinytest.add('observe-sequence - array to cursor', function (test) { +Tinytest.addAsync('observe-sequence - array to cursor', async function (test) { const dep = new Tracker.Dependency; let seq = [{_id: "13", foo: 1}, {_id: "37", bar: 2}]; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -452,7 +452,7 @@ Tinytest.add('observe-sequence - array to cursor', function (test) { }); -Tinytest.add('observe-sequence - cursor to null', function (test) { +Tinytest.addAsync('observe-sequence - cursor to null', async function (test) { const dep = new Tracker.Dependency; const coll = new Mongo.Collection(null); coll.insert({_id: "13", foo: 1}); @@ -460,7 +460,7 @@ Tinytest.add('observe-sequence - cursor to null', function (test) { const cursor = coll.find({}, {sort: {_id: 1}}); let seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -474,14 +474,14 @@ Tinytest.add('observe-sequence - cursor to null', function (test) { ]); }); -Tinytest.add('observe-sequence - cursor to array', function (test) { +Tinytest.addAsync('observe-sequence - cursor to array', async function (test) { const dep = new Tracker.Dependency; const coll = new Mongo.Collection(null); coll.insert({_id: "13.5", foo: 1}); const cursor = coll.find({}, {sort: {_id: 1}}); let seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -497,13 +497,13 @@ Tinytest.add('observe-sequence - cursor to array', function (test) { ]); }); -Tinytest.add('observe-sequence - cursor', function (test) { +Tinytest.addAsync('observe-sequence - cursor', async function (test) { const coll = new Mongo.Collection(null); coll.insert({_id: "13", rank: 1}); const cursor = coll.find({}, {sort: {rank: 1}}); const seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { return seq; }, async function () { await coll.insertAsync({_id: "37", rank: 2}); @@ -527,14 +527,14 @@ Tinytest.add('observe-sequence - cursor', function (test) { ]); }); -Tinytest.add('observe-sequence - cursor to other cursor', function (test) { +Tinytest.addAsync('observe-sequence - cursor to other cursor', async function (test) { const dep = new Tracker.Dependency; const coll = new Mongo.Collection(null); coll.insert({_id: "13", foo: 1}); const cursor = coll.find({}, {sort: {_id: 1}}); let seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -555,7 +555,7 @@ Tinytest.add('observe-sequence - cursor to other cursor', function (test) { ]); }); -Tinytest.add('observe-sequence - cursor to other cursor with transform', function (test) { +Tinytest.addAsync('observe-sequence - cursor to other cursor with transform', async function (test) { const dep = new Tracker.Dependency; const transform = function(doc) { return Object.assign({idCopy: doc._id}, doc); @@ -566,7 +566,7 @@ Tinytest.add('observe-sequence - cursor to other cursor with transform', functio const cursor = coll.find({}, {sort: {_id: 1}}); let seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -587,14 +587,14 @@ Tinytest.add('observe-sequence - cursor to other cursor with transform', functio ]); }); -Tinytest.add('observe-sequence - cursor to same cursor', function (test) { +Tinytest.addAsync('observe-sequence - cursor to same cursor', async function (test) { const coll = new Mongo.Collection(null); coll.insert({_id: "13", rank: 1}); const cursor = coll.find({}, {sort: {rank: 1}}); const seq = cursor; const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -613,11 +613,11 @@ Tinytest.add('observe-sequence - cursor to same cursor', function (test) { ]); }); -Tinytest.add('observe-sequence - string arrays', function (test) { +Tinytest.addAsync('observe-sequence - string arrays', async function (test) { let seq = ['A', 'B']; const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -631,11 +631,11 @@ Tinytest.add('observe-sequence - string arrays', function (test) { ]); }); -Tinytest.add('observe-sequence - number arrays', function (test) { +Tinytest.addAsync('observe-sequence - number arrays', async function (test) { let seq = [1, 1, 2]; const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -651,11 +651,11 @@ Tinytest.add('observe-sequence - number arrays', function (test) { ]); }); -Tinytest.add('observe-sequence - subclassed number arrays', function (test) { +Tinytest.addAsync('observe-sequence - subclassed number arrays', async function (test) { let seq = new ArraySubclass(1, 1, 2); const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -671,11 +671,11 @@ Tinytest.add('observe-sequence - subclassed number arrays', function (test) { ]); }); -Tinytest.add('observe-sequence - vm generated number arrays', function (test) { +Tinytest.addAsync('observe-sequence - vm generated number arrays', async function (test) { let seq = runInVM('new Array(1, 1, 2)'); const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -691,11 +691,11 @@ Tinytest.add('observe-sequence - vm generated number arrays', function (test) { ]); }); -Tinytest.add('observe-sequence - number arrays, _id:0 correctly handled, no duplicate ids warning #4049', function (test) { +Tinytest.addAsync('observe-sequence - number arrays, _id:0 correctly handled, no duplicate ids warning #4049', async function (test) { let seq = [...Array(3).keys()].map(function (i) { return { _id: i}; }); const dep = new Tracker.Dependency; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { @@ -716,7 +716,7 @@ Tinytest.add('observe-sequence - number arrays, _id:0 correctly handled, no dupl ]); }); -Tinytest.add('observe-sequence - cursor to other cursor, same collection', function (test) { +Tinytest.addAsync('observe-sequence - cursor to other cursor, same collection', async function (test) { const dep = new Tracker.Dependency; const coll = new Mongo.Collection(null); coll.insert({_id: "13", foo: 1}); @@ -724,7 +724,7 @@ Tinytest.add('observe-sequence - cursor to other cursor, same collection', funct const cursor = coll.find({foo: 1}); let seq = cursor; - runOneObserveSequenceTestCase(test, function () { + await runOneObserveSequenceTestCase(test, function () { dep.depend(); return seq; }, function () { diff --git a/packages/spacebars-tests/old_templates_tests.js b/packages/spacebars-tests/old_templates_tests.js index e2d6406de..9e9bf1393 100644 --- a/packages/spacebars-tests/old_templates_tests.js +++ b/packages/spacebars-tests/old_templates_tests.js @@ -575,7 +575,7 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - old - template_tests - each on cursor', async function (test) { const tmpl = Template.old_spacebars_template_test_each; @@ -680,7 +680,7 @@ Tinytest.add('spacebars-tests - old - template_tests - ..', function (test) { ); }); -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - old - template_tests - select tags', async function (test) { const tmpl = Template.old_spacebars_template_test_select_tag; @@ -2325,7 +2325,7 @@ Tinytest.add( ); // https://github.com/meteor/meteor/issues/2156 -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - old - template_tests - each with inserts inside autorun', async function (test) { const tmpl = Template.old_spacebars_test_each_with_autorun_insert; diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index 022e06cef..e0f70aa1a 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -681,7 +681,7 @@ Tinytest.add('spacebars-tests - template_tests - if in with', function (test) { divRendersTo(test, div, 'bar bar'); }); -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - template_tests - each on cursor', async function (test) { const tmpl = Template.spacebars_template_test_each; @@ -795,7 +795,7 @@ Tinytest.add('spacebars-tests - template_tests - ..', function (test) { ); }); -Tinytest.add('spacebars-tests - template_tests - select tags', async function (test) { +Tinytest.addAsync('spacebars-tests - template_tests - select tags', async function (test) { const tmpl = Template.spacebars_template_test_select_tag; // {label: (string)} @@ -2828,7 +2828,7 @@ Tinytest.add( ); // https://github.com/meteor/meteor/issues/2156 -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - template_tests - each with inserts inside autorun', async function (test) { const tmpl = Template.spacebars_test_each_with_autorun_insert; @@ -4131,7 +4131,7 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'spacebars-tests - template_tests - #each @index', async function (test) { const tmpl = Template.spacebars_template_test_each_index; From 470ef7cc7e551f726ebf4e5195f4c4cfe617b629 Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Tue, 31 Mar 2026 01:33:55 +0200 Subject: [PATCH 05/16] tests: fully remove jquery from all tests and fix delegation --- packages/blaze/.versions | 1 - packages/blaze/blaze.d.ts | 2 - packages/blaze/dombackend.js | 98 +++++++++++++------ packages/blaze/package.js | 4 +- packages/blaze/render_tests.js | 26 ++--- packages/spacebars-tests/.versions | 1 - .../spacebars-tests/old_templates_tests.js | 54 +++++++--- packages/spacebars-tests/package.js | 1 - packages/spacebars-tests/template_tests.js | 68 +++++++++---- test-app/package.json | 1 - 10 files changed, 174 insertions(+), 82 deletions(-) diff --git a/packages/blaze/.versions b/packages/blaze/.versions index e51307103..3cdab109f 100644 --- a/packages/blaze/.versions +++ b/packages/blaze/.versions @@ -29,7 +29,6 @@ html-tools@2.0.0 htmljs@2.0.1 id-map@1.2.0 inter-process-messaging@0.1.2 -jquery@3.0.2 local-test:blaze@3.0.1 logging@1.3.5 meteor@2.0.1 diff --git a/packages/blaze/blaze.d.ts b/packages/blaze/blaze.d.ts index 9a2896ca6..c27e6d04c 100644 --- a/packages/blaze/blaze.d.ts +++ b/packages/blaze/blaze.d.ts @@ -1,5 +1,3 @@ -import * as $ from 'jquery'; - import { Tracker } from 'meteor/tracker'; import { Meteor } from 'meteor/meteor'; declare module 'meteor/blaze' { diff --git a/packages/blaze/dombackend.js b/packages/blaze/dombackend.js index ca2eba9e3..e32b085a6 100644 --- a/packages/blaze/dombackend.js +++ b/packages/blaze/dombackend.js @@ -1,12 +1,17 @@ const DOMBackend = {}; Blaze._DOMBackend = DOMBackend; -const $jq = (typeof jQuery !== 'undefined' ? jQuery : - (typeof Package !== 'undefined' && Package.jquery ? - (Package.jquery.jQuery || Package.jquery.$) : null)); +let $jq; -const _hasJQuery = !!$jq; +if (typeof jQuery !== 'undefined') { + $jq = jQuery; +} + +if (!$jq && typeof Package !== 'undefined' && Package.jquery) { + $jq = Package.jquery.jQuery ?? Package.jquery.$ ?? null; +} +const _hasJQuery = !!$jq; if (_hasJQuery && typeof console !== 'undefined') { console.info( '[Blaze] jQuery detected as DOM backend. Native DOM backend is available — ' + @@ -37,7 +42,7 @@ DOMBackend.parseHTML = function (html) { if (_hasJQuery) { return $jq.parseHTML(html, DOMBackend.getContext()) || []; } - const template = document.createElement('template'); + const template = DOMBackend.getContext().createElement('template'); template.innerHTML = html; return Array.from(template.content.childNodes); }; @@ -63,19 +68,7 @@ DOMBackend.Events = { // Alias non-bubbling events to their bubbling equivalents eventType = _delegateEventAlias[eventType] || eventType; - const wrapper = (event) => { - // event.target can be a text node (nodeType 3) — walk to parent element first - const origin = event.target; - const target = origin.nodeType === 1 ? origin.closest(selector) : origin.parentElement?.closest(selector); - if (target && elem.contains(target)) { - // Mimic jQuery's delegated event behavior - Object.defineProperty(event, 'currentTarget', { - value: target, - configurable: true, - }); - handler.call(target, event); - } - }; + const wrapper = createWrapper(elem, type, selector, handler); if (!_delegateMap.has(elem)) { _delegateMap.set(elem, new Map()); @@ -123,20 +116,7 @@ DOMBackend.Events = { handler._meteorui_wrapper = wrapper; } else { - const wrapper = (event) => { - // event.target can be a text node — walk to parent element first - const origin = event.target; - const matched = origin.nodeType === 1 ? origin.closest(selector) : origin.parentElement?.closest(selector); - if (matched && elem.contains(matched)) { - Object.defineProperty(event, 'currentTarget', { - value: matched, - configurable: true, - }); - handler.call(elem, event); - } - }; - - handler._meteorui_wrapper = wrapper; + handler._meteorui_wrapper = createWrapper(elem, type, selector, handler); } type = DOMBackend.Events.parseEventType(type); @@ -158,6 +138,46 @@ DOMBackend.Events = { } }; +const createWrapper = (elem, type, selector, handler) => { + return (event) => { + // event.target can be a text node (nodeType 3) — walk to parent element first + const origin = event.target; + const target = origin.nodeType === 1 ? origin.closest(selector) : origin.parentElement?.closest(selector); + + // we need to manually check, if a selector left the template scope + // which jQuery would do automatically for us. + // for this we traverse nodes that still match the selector + // and compare the final to see, if the event bubbled up to + // a parent view that is out of scope + let node = origin; + while (node && node !== elem && node instanceof Element && node.matches(selector)) { + node = node.parentElement; + } + + const root = elem?.['$blaze_range']?.view?.name; + const scope = node?.['$blaze_range']?.view?.name; + + let inScope = true; + if (root && scope && root === scope) { + inScope = false; + } + + if (target && elem.contains(target) && inScope) { + // Mimic jQuery's delegated event behavior + Object.defineProperty(event, 'currentTarget', { + value: target, + configurable: true, + }); + // mimic jQuery event return false behavior + const value = handler.call(target, event); + if (value === false) { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + } + } + }; +} ///// Removal detection and interoperability. @@ -274,6 +294,20 @@ if (_hasJQuery) { _executeTeardownCallbacks(this); } }; +} else { + // in native DOM Backend we need to extend the native remove function + // to call the TearDown callbacks, registered during materializing + // for the element and its children. + (function(removeFn) { + HTMLElement.prototype.remove = function () { + _executeTeardownCallbacks(this); + for (const child of this.children) { + _executeTeardownCallbacks(child); + } + return removeFn.apply(this, arguments); + }; + })(HTMLElement.prototype.remove); + } diff --git a/packages/blaze/package.js b/packages/blaze/package.js index 50f85c7ea..39124a44f 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -6,7 +6,6 @@ Package.describe({ }); Package.onUse(function (api) { - api.use('jquery@1.11.9 || 3.0.0', { weak: true }); // should be a weak dep, by having multiple "DOM backends" api.use('tracker@1.3.2'); api.use('check@1.0.12'); api.use('observe-sequence@2.0.0'); @@ -53,8 +52,7 @@ Package.onTest(function (api) { api.use('ecmascript@0.16.9'); api.use('tinytest'); api.use('test-helpers'); - api.use('jquery@1.11.9 || 3.0.0'); // strong dependency, for testing jQuery backend - + api.use('jquery') api.use('reactive-var@1.0.12'); api.use('tracker@1.3.2'); diff --git a/packages/blaze/render_tests.js b/packages/blaze/render_tests.js index 67df25b72..9e907ba9c 100644 --- a/packages/blaze/render_tests.js +++ b/packages/blaze/render_tests.js @@ -1,7 +1,7 @@ import { BlazeTools } from 'meteor/blaze-tools'; const toCode = BlazeTools.toJS; - +const hasJquery = Blaze._DOMBackend._hasJQuery; const P = HTML.P; const CharRef = HTML.CharRef; const DIV = HTML.DIV; @@ -267,8 +267,8 @@ Tinytest.add("blaze - render - view GC", function (test) { (function () { const R = ReactiveVar('Hello'); const test1 = P(Blaze.View(function () { return R.get(); })); - const div = document.createElement("DIV"); + materialize(test1, div); test.equal(canonicalizeHtml(div.innerHTML), "

Hello

"); @@ -278,7 +278,7 @@ Tinytest.add("blaze - render - view GC", function (test) { test.equal(R._numListeners(), 1); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(R._numListeners(), 0); @@ -325,7 +325,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { test.equal(canonicalizeHtml(div.innerHTML), ''); test.equal(R._numListeners(), 1); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(R._numListeners(), 0); })(); @@ -354,7 +354,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { Tracker.flush(); test.equal(canonicalizeHtml(div.innerHTML), '
'); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(style._numListeners(), 0); })(); @@ -393,7 +393,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { test.equal(canonicalizeHtml(div.innerHTML), ''); test.equal(R._numListeners(), 1); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(R._numListeners(), 0); })(); @@ -482,7 +482,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) { Tracker.flush(); test.equal(canonicalizeHtml(div.innerHTML), ''); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(R._numListeners(), 0); })(); @@ -574,7 +574,7 @@ Tinytest.add("blaze - render - templates and views", function (test) { test.equal(canonicalizeHtml(div.innerHTML), '123
'); buf.length = 0; - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } buf.sort(); test.equal(buf, ['destroyed 1', 'destroyed 2', 'destroyed 3']); @@ -617,10 +617,12 @@ Tinytest.add("blaze - render - findAll", function (test) { Blaze.render(myTemplate, div); Tracker.flush(); - test.equal(Array.isArray(found), true); - test.equal(Array.isArray($found), false); test.equal(found.length, 2); test.equal($found.length, 2); + const foundIsArray = Array.isArray(found); + const $foundIsArray = Array.isArray($found); + test.equal(foundIsArray, true); + test.equal($foundIsArray, !hasJquery); }); Tinytest.add("blaze - render - reactive attributes 2", function (test) { @@ -670,7 +672,7 @@ Tinytest.add("blaze - render - reactive attributes 2", function (test) { // clean up - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(R1._numListeners(), 0); test.equal(R2._numListeners(), 0); @@ -778,7 +780,7 @@ if (typeof MutationObserver !== 'undefined') { // We do not update anything after initial rendering, so only one mutation is there. test.equal(observedMutations.length, 1); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } observer.disconnect(); onComplete(); diff --git a/packages/spacebars-tests/.versions b/packages/spacebars-tests/.versions index 1d782853c..b311664f4 100644 --- a/packages/spacebars-tests/.versions +++ b/packages/spacebars-tests/.versions @@ -30,7 +30,6 @@ html-tools@2.0.0 htmljs@2.0.1 id-map@1.2.0 inter-process-messaging@0.1.2 -jquery@3.0.2 local-test:spacebars-tests@2.0.1 logging@1.3.5 markdown@2.0.0 diff --git a/packages/spacebars-tests/old_templates_tests.js b/packages/spacebars-tests/old_templates_tests.js index 9e9bf1393..b63227d60 100644 --- a/packages/spacebars-tests/old_templates_tests.js +++ b/packages/spacebars-tests/old_templates_tests.js @@ -2,7 +2,7 @@ // This file is used to ensure old built templates still work with the // new Blaze APIs. More in a comment at the top of old_templates.js // - +const hasJquery = Blaze._DOMBackend._hasJQuery; const divRendersTo = function (test, div, html) { Tracker.flush({ _throwFirstError: true }); const actual = canonicalizeHtml(div.innerHTML); @@ -1031,7 +1031,7 @@ Tinytest.add( divRendersTo(test, div, 'x'); // trigger #each component destroyed - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } // insert another document. cursor should no longer be observed so // should have no effect. @@ -1890,7 +1890,7 @@ const runOneTwoTest = function (test, subTemplateName, optionsData) { test.equal(buf, '121'); // clean up the div - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(showOne._numListeners(), 0); test.equal(dummy._numListeners(), 0); }); @@ -1987,7 +1987,7 @@ Tinytest.add( document.body.appendChild(div); clickElement(div.querySelector('button')); Tracker.flush(); // rendered gets called afterFlush - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.isFalse(dataInHelper === window); test.equal(dataInHelper, {}); @@ -2155,6 +2155,7 @@ Tinytest.add( } ); +if (hasJquery) { Tinytest.add( 'spacebars-tests - old - template_tests - jQuery.trigger extraParameters are passed to the event callback', function (test) { @@ -2182,6 +2183,7 @@ Tinytest.add( test.equal(captured, true); } ); +} Tinytest.add( 'spacebars-tests - old - template_tests - toHTML', @@ -2528,7 +2530,11 @@ Tinytest.add( test.equal(helperCalled, true); helperCalled = false; - $(div).find('.test-with-cleanup').remove(); + if (hasJquery) { + $(div).find('.test-with-cleanup').remove(); + } else { + div.querySelector('.test-with-cleanup').remove(); + } rv.set('second'); Tracker.flush(); @@ -2633,7 +2639,7 @@ Tinytest.add( divRendersTo(test, div, '
C
'); test.equal(buf, 'CaRaDaCbRbDbCcRc'); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(buf, 'CaRaDaCbRbDbCcRcDc'); } ); @@ -2784,11 +2790,21 @@ Tinytest.add( // Now see that removing the DOM with jQuery, below // the level of the entire template, stops everything. - $(div.querySelector('.toremove')).remove(); + if (hasJquery) { + $(div.querySelector('.toremove')).remove(); + } else { + div.querySelector('.toremove').remove(); + } assertCallsAndListeners(0, 0, 0, 0); } ); + +const trigger = (el, eventType, bubbles = true) => { + const event = new Event(eventType, { bubbles: bubbles, cancelable: true }); + el.dispatchEvent(event); +} + Tinytest.add( 'spacebars-tests - old - template_tests - focus/blur with clean-up', function (test) { @@ -2833,11 +2849,21 @@ Tinytest.add( 'You might need to defocus the Chrome Dev Tools to get a more accurate run of this test!', }); borken = true; - $(input).trigger('focus'); + if (hasJquery) { + $(input).trigger('focus'); + } else { + trigger(input, 'focusin', true); + } } test.equal(buf.join(), 'FOCUS'); blurElement(div.querySelector('input')); - if (buf.length === 1) $(input).trigger('blur'); + if (buf.length === 1) { + if (hasJquery) { + $(input).trigger('blur'); + } else { + trigger(input, 'focusout', true); + } + } test.equal(buf.join(), 'FOCUS,BLUR'); // now switch the IF and check again. The failure mode @@ -2852,7 +2878,13 @@ Tinytest.add( Tracker.flush(); test.equal(div.querySelectorAll('input').length, 1); focusElement((input = div.querySelector('input'))); - if (borken) $(input).trigger('focus'); + if (borken) { + if (hasJquery) { + $(input).trigger('focus'); + } else { + trigger(input, 'focusin', true); + } + } test.equal(buf.join(), 'FOCUS'); blurElement(div.querySelector('input')); if (!borken) test.equal(buf.join(), 'FOCUS,BLUR'); @@ -2947,7 +2979,7 @@ Tinytest.add( test.equal(canonicalizeHtml(div.innerHTML), 'blah'); document.body.appendChild(div); clickElement(div.querySelector('span')); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.isTrue(currentView); test.equal(currentData, 'blah'); diff --git a/packages/spacebars-tests/package.js b/packages/spacebars-tests/package.js index 6fa5f2664..e0664960c 100644 --- a/packages/spacebars-tests/package.js +++ b/packages/spacebars-tests/package.js @@ -11,7 +11,6 @@ Package.onTest(function (api) { api.use([ 'es5-shim@4.8.0', 'tinytest', - 'jquery@1.11.9 || 3.0.0', 'test-helpers', 'reactive-var', 'markdown@1.0.14 || 2.0.0 || 3.0.0-beta300.7', diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index e0f70aa1a..8e015bcb8 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -1,3 +1,4 @@ +const hasJquery = Blaze._DOMBackend._hasJQuery; const divRendersTo = function (test, div, html) { Tracker.flush({ _throwFirstError: true }); const actual = canonicalizeHtml(div.innerHTML); @@ -1198,7 +1199,7 @@ Tinytest.add( divRendersTo(test, div, 'x'); // trigger #each component destroyed - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } // insert another document. cursor should no longer be observed so // should have no effect. @@ -2309,7 +2310,7 @@ const runOneTwoTest = function (test, subTemplateName, optionsData) { test.equal(buf, '121'); // clean up the div - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(showOne._numListeners(), 0); test.equal(dummy._numListeners(), 0); }); @@ -2404,7 +2405,7 @@ Tinytest.add( document.body.appendChild(div); clickElement(div.querySelector('button')); Tracker.flush(); // rendered gets called afterFlush - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.isFalse(dataInHelper === window); test.equal(dataInHelper, {}); @@ -2473,7 +2474,8 @@ Tinytest.add( const div = renderToDiv(tmpl); document.body.appendChild(div); - clickIt(document.getElementById(elemId)); + const target = document.getElementById(elemId); + clickIt(target); // NOTE: This failure can stick across test runs! Try // removing '#bad-url' from the location bar and run // the tests again. :) @@ -2501,6 +2503,7 @@ Tinytest.add( const div = renderToDiv(tmpl); document.body.appendChild(div); test.equal(buf.join(), ''); + clickIt(div.querySelector('.p1')); test.equal(buf.join(), ''); clickIt(div.querySelector('.p2')); @@ -2510,7 +2513,7 @@ Tinytest.add( ); if (document.addEventListener) { - // see note about non-bubbling events in the "capuring events" + // see note about non-bubbling events in the "capturing events" // templating test for why we use the VIDEO tag. (It would be // nice to get rid of the network dependency, though.) // We skip this test in IE 8. @@ -2529,18 +2532,16 @@ if (document.addEventListener) { const div = renderToDiv(tmpl); document.body.appendChild(div); test.equal(buf.join(), ''); - simulateEvent( + trigger( div.querySelector('.video1'), 'play', - {}, - { bubbles: false } + false ); test.equal(buf.join(), ''); - simulateEvent( + trigger( div.querySelector('.video2'), 'play', - {}, - { bubbles: false } + false ); test.equal(buf.join(), 'video2'); document.body.removeChild(div); @@ -2568,6 +2569,7 @@ Tinytest.add('spacebars-tests - template_tests - tables', function (test) { divRendersTo(test, div, '
Foo
'); }); +if (hasJquery){ Tinytest.add( 'spacebars-tests - template_tests - jQuery.trigger extraParameters are passed to the event callback', function (test) { @@ -2595,6 +2597,7 @@ Tinytest.add( test.equal(captured, true); } ); +} Tinytest.add('spacebars-tests - template_tests - toHTML', function (test) { // run once, verifying that autoruns are stopped @@ -3039,7 +3042,11 @@ Tinytest.add( test.equal(helperCalled, true); helperCalled = false; - $(div).find('.test-with-cleanup').remove(); + if (hasJquery) { + $(div).find('.test-with-cleanup').remove(); + } else { + div.querySelector('.test-with-cleanup').remove(); + } rv.set('second'); Tracker.flush(); @@ -3152,7 +3159,7 @@ Tinytest.add( divRendersTo(test, div, '
C
'); test.equal(buf, 'CaRaDaCbRbDbCcRc'); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.equal(buf, 'CaRaDaCbRbDbCcRcDc'); } ); @@ -3309,11 +3316,20 @@ Tinytest.add( // Now see that removing the DOM with jQuery, below // the level of the entire template, stops everything. - $(div.querySelector('.toremove')).remove(); + if (hasJquery) { + $(div.querySelector('.toremove')).remove(); + } else { + div.querySelector('.toremove').remove(); + } assertCallsAndListeners(0, 0, 0, 0); } ); +const trigger = (el, eventType, bubbles = true) => { + const event = new Event(eventType, { bubbles: bubbles, cancelable: true }); + el.dispatchEvent(event); +} + Tinytest.add( 'spacebars-tests - template_tests - focus/blur with clean-up', function (test) { @@ -3360,11 +3376,21 @@ Tinytest.add( 'You might need to defocus the Chrome Dev Tools to get a more accurate run of this test!', }); borken = true; - $(input).trigger('focus'); + if (hasJquery) { + $(input).trigger('focus'); + } else { + trigger(input, 'focusin', true); + } } test.equal(buf.join(), 'FOCUS'); blurElement(div.querySelector('input')); - if (buf.length === 1) $(input).trigger('blur'); + if (buf.length === 1) { + if (hasJquery) { + $(input).trigger('blur'); + } else { + trigger(input, 'focusout', true); + } + } test.equal(buf.join(), 'FOCUS,BLUR'); // now switch the IF and check again. The failure mode @@ -3379,7 +3405,13 @@ Tinytest.add( Tracker.flush(); test.equal(div.querySelectorAll('input').length, 1); focusElement((input = div.querySelector('input'))); - if (borken) $(input).trigger('focus'); + if (borken) { + if (hasJquery) { + $(input).trigger('focus'); + } else { + trigger(input, 'focusin', true); + } + } test.equal(buf.join(), 'FOCUS'); blurElement(div.querySelector('input')); if (!borken) test.equal(buf.join(), 'FOCUS,BLUR'); @@ -3478,7 +3510,7 @@ Tinytest.add( test.equal(canonicalizeHtml(div.innerHTML), 'blah'); document.body.appendChild(div); clickElement(div.querySelector('span')); - $(div).remove(); + if (hasJquery) { $(div).remove() } else { div.remove() } test.isTrue(currentView); test.equal(currentData, 'blah'); diff --git a/test-app/package.json b/test-app/package.json index 951576f4d..bd43b3763 100644 --- a/test-app/package.json +++ b/test-app/package.json @@ -15,7 +15,6 @@ "license": "MIT", "dependencies": { "@babel/runtime": "^7.29.2", - "jquery": "^4.0.0", "meteor-node-stubs": "^1.2.27", "puppeteer": "^24.40.0" }, From 1a43798e0217a57e47247c4b3430eb2f5822ed6a Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Tue, 31 Mar 2026 10:15:19 +0200 Subject: [PATCH 06/16] tests: include jquery based on env flag in tests --- packages/blaze/dombackend.js | 22 ++++++++++++++++++---- packages/blaze/package.js | 7 +++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/blaze/dombackend.js b/packages/blaze/dombackend.js index e32b085a6..0ba695760 100644 --- a/packages/blaze/dombackend.js +++ b/packages/blaze/dombackend.js @@ -2,27 +2,41 @@ const DOMBackend = {}; Blaze._DOMBackend = DOMBackend; let $jq; +let $jqSource; -if (typeof jQuery !== 'undefined') { +try { + const p = require.resolve('jquery'); + if (typeof p === 'string') { + $jq = require('jquery'); + $jqSource = `npm dependency (${p})`; + } +} catch {} + +if (!$jq && typeof jQuery !== 'undefined') { $jq = jQuery; + $jqSource = 'global scope'; } if (!$jq && typeof Package !== 'undefined' && Package.jquery) { $jq = Package.jquery.jQuery ?? Package.jquery.$ ?? null; + $jqSource = 'Meteor packages'; } const _hasJQuery = !!$jq; if (_hasJQuery && typeof console !== 'undefined') { + const version = jQuery.fn?.jquery ?? ' '; console.info( - '[Blaze] jQuery detected as DOM backend. Native DOM backend is available — ' + - 'remove the jquery package to enable it. jQuery support will be removed in Blaze 4.0.' + `[Blaze] jQuery${version} detected as DOM backend. Native DOM backend is available — ` + + 'remove jquery to enable native DOM backend. jQuery support will be removed in Blaze 4.0.' + ); + console.info( + `[Blaze] jQuery was loaded via ${$jqSource}` ); } DOMBackend._$jq = $jq; // null when absent DOMBackend._hasJQuery = _hasJQuery; - DOMBackend.getContext = function () { if (DOMBackend._context) return DOMBackend._context; // jQuery may need the legacy check; native path always supports createHTMLDocument diff --git a/packages/blaze/package.js b/packages/blaze/package.js index 39124a44f..bd1bf8207 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -12,7 +12,7 @@ Package.onUse(function (api) { api.use('reactive-var@1.0.12'); api.use('ordered-dict@1.2.0'); api.use('ecmascript@0.16.9'); - + api.use('jquery@3.0.0', { weak: true }); api.export([ 'Blaze', 'UI', @@ -52,10 +52,13 @@ Package.onTest(function (api) { api.use('ecmascript@0.16.9'); api.use('tinytest'); api.use('test-helpers'); - api.use('jquery') api.use('reactive-var@1.0.12'); api.use('tracker@1.3.2'); + if (process.env.TEST_USE_JQUERY) { + api.use('jquery@3.0.0'); + } + api.use('blaze'); api.use('blaze-tools@2.0.0'); // for BlazeTools.toJS api.use('html-tools@2.0.0'); From 4a71b4f26067aca2916a5a4de7a87c9b4f928c37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:36:02 +0200 Subject: [PATCH 07/16] Bump lodash from 4.17.21 to 4.18.1 in /site (#498) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package-lock.json | 188 ++--------------------------------------- 1 file changed, 5 insertions(+), 183 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index 17f9335c0..551c0be99 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -148,21 +148,6 @@ "node": ">=0.10.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -370,20 +355,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -430,20 +401,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browser-fingerprint": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/browser-fingerprint/-/browser-fingerprint-0.0.1.tgz", @@ -651,32 +608,6 @@ "hexo": "bin/hexo" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1350,20 +1281,6 @@ "node": ">=0.10.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -1467,21 +1384,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1613,20 +1515,6 @@ "node": ">=0.10.0" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3796,20 +3684,6 @@ "node": ">= 0.10" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -3913,17 +3787,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -4299,10 +4162,11 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.assignin": { "version": "4.2.0", @@ -5241,20 +5105,6 @@ "dev": true, "optional": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -5383,20 +5233,6 @@ "node": ">= 6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", @@ -6400,20 +6236,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", From d0c18d19d16bc0897be722f81fd5cbd1058bb710 Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Wed, 8 Apr 2026 09:24:02 +0200 Subject: [PATCH 08/16] packages: add modified test-in-browser package for on/off jQuery in tests --- packages/test-in-browser/.gitignore | 1 + .../test-in-browser/.npm/package/.gitignore | 1 + packages/test-in-browser/.npm/package/README | 7 + .../.npm/package/npm-shrinkwrap.json | 20 + packages/test-in-browser/README.md | 5 + packages/test-in-browser/driver.css | 281 +++++++ packages/test-in-browser/driver.html | 180 +++++ packages/test-in-browser/driver.js | 710 ++++++++++++++++++ packages/test-in-browser/package.js | 42 ++ packages/test-in-browser/server.js | 6 + 10 files changed, 1253 insertions(+) create mode 100644 packages/test-in-browser/.gitignore create mode 100644 packages/test-in-browser/.npm/package/.gitignore create mode 100644 packages/test-in-browser/.npm/package/README create mode 100644 packages/test-in-browser/.npm/package/npm-shrinkwrap.json create mode 100644 packages/test-in-browser/README.md create mode 100644 packages/test-in-browser/driver.css create mode 100644 packages/test-in-browser/driver.html create mode 100644 packages/test-in-browser/driver.js create mode 100644 packages/test-in-browser/package.js create mode 100644 packages/test-in-browser/server.js diff --git a/packages/test-in-browser/.gitignore b/packages/test-in-browser/.gitignore new file mode 100644 index 000000000..a7a45c088 --- /dev/null +++ b/packages/test-in-browser/.gitignore @@ -0,0 +1 @@ +.build* \ No newline at end of file diff --git a/packages/test-in-browser/.npm/package/.gitignore b/packages/test-in-browser/.npm/package/.gitignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/packages/test-in-browser/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/test-in-browser/.npm/package/README b/packages/test-in-browser/.npm/package/README new file mode 100644 index 000000000..3d492553a --- /dev/null +++ b/packages/test-in-browser/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/test-in-browser/.npm/package/npm-shrinkwrap.json b/packages/test-in-browser/.npm/package/npm-shrinkwrap.json new file mode 100644 index 000000000..cc5e89b5b --- /dev/null +++ b/packages/test-in-browser/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,20 @@ +{ + "lockfileVersion": 4, + "dependencies": { + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, + "bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==" + }, + "diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==" + } + } +} diff --git a/packages/test-in-browser/README.md b/packages/test-in-browser/README.md new file mode 100644 index 000000000..596144712 --- /dev/null +++ b/packages/test-in-browser/README.md @@ -0,0 +1,5 @@ +# test-in-browser +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/test-in-browser) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/test-in-browser) +*** + +> Attention! This package is a stub package and should be removed, once the original package has jquery removed. \ No newline at end of file diff --git a/packages/test-in-browser/driver.css b/packages/test-in-browser/driver.css new file mode 100644 index 000000000..a5fc00347 --- /dev/null +++ b/packages/test-in-browser/driver.css @@ -0,0 +1,281 @@ + +* { + /* Variables */ + /* for reference: https://tailwindcss.com/docs/customizing-colors */ + + --bg-black: #18181b; + --neutral-black: #343333; + --primary-white: #F9FAFB; + --red-50: #fef2f2; + --red-200: #fecaca; + --red-400: #ea5555; + --red-600: #dc2626; + --red-900: #7f1d1d; + --gray-300: #d6d3d1; + --gray-400: #a09d9a; + --gray-500: #737373; + + --blue-gray-400: #5f6373; + + + --blue-500: #6366f1; + --blue-50: #eef2ff; + + --yellow-400: #facc15; + + --green-600: #16a34a; +} + +body { + height: 100vh; + width: 100vw; + background-color: #18181b !important; + background-color: var(--bg-black) !important; + color: #F9FAFB !important; + color: var(--primary-white) !important; + +} + +.test-in-browser { + display: flex; + height: 100%; + flex-direction: column; +} + +.test-results { + flex: 1; + overflow: auto; +} + +#testProgressBar { + flex: 1; + max-width: 400px; + position: relative; +} + +.header { + font-family: Arial, sans-serif; + font-size: 24px; + padding-bottom: 4px; +} + +.header.in-progress { + color: #fef2f2; + color: var(--red-50); +} + +.header.pass { + color: #16a34a; + color: var(--green-600); /* green */ +} + +.header.fail { + color: var(--red-400); + color: #ea5555; + font-weight: bold; +} + +.header .time { + color: #737373; + font-size: 14px; +} + +.test_table { + font-family: Arial, sans-serif; + font-size: 16px; +} + +.test_table .group { + border-left: 1px solid #313131; + border-left: 1px solid var(--neutral-black); +} + +.test_table .group .group { + margin-left: 20px; +} + +.test_table .test { + margin-left: 20px; +} + +.test_table .testname { + margin-left: 200px; + line-height: 24px; + vertical-align: middle; + text-decoration: underline; + cursor: pointer; +} + +.test_table .groupname { + font-weight: bold; + background: var(--neutral-black); + background: #313131; + padding-left: 5px; + line-height: 24px; + vertical-align: middle; + font-size: 16px; + color: var(--primary-white); + color: #F9FAFB; +} + +.test_table .event { + margin-left: 20px; + font-size: 14px; + border-left: 2px solid var(--blue-gray-400); + border-left: 2px solid #5f6373; + padding: 4px; + position: relative; +} + +.test_table .test .testrow { + position: relative; + overflow: hidden; /*hasLayout*/ +} + +.test_table .running .testname { + color: var(--blue-500); + color: #6366f1; +} + +.test_table .failed .testname { + color: var(--red-400); + color: #ea5555; +} + +.test_table .succeeded .testname { + color: var(--green-600); + color: #16a34a; +} + +.test_table .teststatus { + position: absolute; + height: 100%; + width: 100px; + left: 0px; + top: 0; + text-align: left; + line-height: 24px; + vertical-align: middle; +} + +.test_table .testtime { + position: absolute; + height: 100%; + width: 75px; + margin-right: 5px; + left: 100px; + top: 0; + text-align: right; + line-height: 24px; + vertical-align: middle; + font-size: 14px; + color: var(--gray-500); + color: #737373; +} + +.test_table .succeeded .teststatus { + color: var(--green-600); + background: var(--bg-black); + color: #16a34a; + background: #18181b; +} + +.test_table .failed .teststatus { + color: var(--red-400); /* red */ + background: var(--bg-black); + color: #ea5555; /* red */ + background: #18181b; +} + +.test_table .running .teststatus { + color: var(--red-50); + color: #fef2f2; +} + +.test_table .event .expected_fail { + color: var(--red-400); + color: #ea5555; +} + +.test_table .event .fail { + color: var(--red-400); + color: #ea5555; +} + +.test_table .event .exception { + color: var(--red-400); + color: #ea5555; +} + +.exception pre { + color: var(--primary-white); + color: #F9FAFB; +} + +.test_table .event .nodata { + color: var(--red-50); + color: #fef2f2; + font-style: italic; +} + +.test_table .event .xtimes, .test_table .event .failkey { + font-weight: bold; +} + +.test_table .event .debug { + display: none; +} + +.test_table .event:hover .debug { + display: inline; + color: var(--gray-400); + color: #a09d9a; + text-decoration: underline; + cursor: pointer; +} + +.in-progress { + position: absolute; + left: 10px; + z-index: 2; +} + +.string_equal { + line-height: 1.2; + margin-left: 30px; +} + +.string_equal ins { + text-decoration: none; +} + +.string_equal_expected ins { + background: #f59e0b; +} + +.string_equal_actual ins { + color: var(--red-600); + background: var(--red-200); + color: #dc2626; + background: #fecaca; +} + +#current-client-test { + color: var(--gray-300); + color: #d6d3d1; + margin-right: 15px; +} + +.failedTests { + color: var(--red-400); + color: #ea5555; +} + +#testProgressBar { + background: --var(--blue-gray-400); + background: #5f6373; +} + +.progress-bar.bg-warning { + background-color: #f59e0b !important; +} \ No newline at end of file diff --git a/packages/test-in-browser/driver.html b/packages/test-in-browser/driver.html new file mode 100644 index 000000000..116024b80 --- /dev/null +++ b/packages/test-in-browser/driver.html @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js new file mode 100644 index 000000000..e05e8c667 --- /dev/null +++ b/packages/test-in-browser/driver.js @@ -0,0 +1,710 @@ +//// +//// Setup +//// +import { diffChars } from 'diff' +import 'bootstrap/dist/css/bootstrap.min.css'; + +const arraysEqual = (a, b) => { + return a.length === b.length && a.every((v, i) => v === b[i]); +} + +// dependency for the count of tests running/passed/failed, etc. drives +// the navbar and the like. +var countDep = new Tracker.Dependency; +// things that change on countDep +var running = true; +var totalCount = 0; +var passedCount = 0; +var failedCount = 0; +var failedTests = []; + +// Dependency for when a new top level group is added. Each group and +// each test have their own dependency objects. +var topLevelGroupsDep = new Tracker.Dependency; + +// An array of top-level groups. +// +// Each group is an object with: +// - name: string +// - path: array of strings (names of parent groups) +// - parent: parent group object (back reference) +// - dep: Tracker.Dependency object for this group. fires when new tests added. +// - groups: list of sub-groups +// - tests: list of tests in this group +// +// Each test is an object with: +// - name: string +// - parent: parent group object (back reference) +// - server: boolean +// - fullName: string +// - dep: Tracker.Dependency object for this test. fires when the test completes. +var resultTree = []; + +Session.set("uncaughtErrors", []); +window.onerror = (message, source, line) => { + const uncaughtErrors = new Set(Session.get("uncaughtErrors")); + uncaughtErrors.add(message); + Session.set("uncaughtErrors", Array.from(uncaughtErrors)); +}; + +var getGroupPathFromURL = function() { + var pathname = window.location.pathname; + var match = pathname.match(/^\/group\/(.+)$/); + if (match) { + try { + return JSON.parse(decodeURIComponent(match[1])); + } catch (e) { + console.warn('Invalid group path in URL:', match[1]); + } + } + return ["tinytest"]; +}; + +var setGroupPathInURL = function(groupPath, pushState = true) { + var newURL = '/'; + + if (!arraysEqual(groupPath, ['tinytest'])) { + newURL = '/group/' + encodeURIComponent(JSON.stringify(groupPath)); + } + + var historyState = { groupPath: groupPath }; + + if (pushState) { + window.history.pushState(historyState, '', newURL); + } else { + window.history.replaceState(historyState, '', newURL); + } +}; + +// Initialize group path from URL, fallback to default +var initialGroupPath = getGroupPathFromURL(); +Session.setDefault("groupPath", initialGroupPath); +Session.set("rerunScheduled", false); + +// Safeguards for rapid navigation +var isNavigating = false; +var lastNavigationTime = 0; +var navigationDebounceMs = 200; // Minimum time between navigations + +// Handle browser back/forward navigation +window.addEventListener('popstate', function(event) { + var now = Date.now(); + + // Safeguard 1: Prevent overlapping navigations + if (isNavigating) { + console.log('Navigation already in progress, ignoring'); + return; + } + + // Safeguard 2: Debounce rapid successive calls + if (now - lastNavigationTime < navigationDebounceMs) { + console.log('Navigation too rapid, ignoring'); + return; + } + + var newGroupPath = getGroupPathFromURL(); + var currentGroupPath = Session.get("groupPath"); + + if (!arraysEqual(newGroupPath, currentGroupPath)) { + // Set navigation flag + isNavigating = true; + lastNavigationTime = now; + + // Emulate the EXACT same sequence as changeToPath + + // 1. URL is already changed by browser, but ensure it's correct + setGroupPathInURL(newGroupPath, false); // replaceState, don't create new entry + + // 2. Update session state (SAME as changeToPath) + Session.set("groupPath", newGroupPath); + Session.set("rerunScheduled", true); + + // 3. Clean reload (SAME as changeToPath) + Reload._reload(); + } +}); + +// This function is exported. It's called on client startup by the +// bundle generated by `meteor test` or `meteor test-packages`. +runTests = function () { + // Reset navigation safeguards when app starts + isNavigating = false; + + // Reset all test state before starting + running = true; + totalCount = 0; + passedCount = 0; + failedCount = 0; + failedTests = []; + resultTree = []; + + // Reset dependencies to trigger UI updates + countDep.changed(); + topLevelGroupsDep.changed(); + + // Get current group path from URL (in case of refresh) + var currentGroupPath = getGroupPathFromURL(); + Session.set("groupPath", currentGroupPath); + + // Only update URL if it's actually different from what we expect + var expectedURL; + if (currentGroupPath && currentGroupPath.length > 0 && JSON.stringify(currentGroupPath) !== JSON.stringify(["tinytest"])) { + expectedURL = '/group/' + encodeURIComponent(JSON.stringify(currentGroupPath)); + } else { + expectedURL = '/'; + } + + // Only do replaceState if the URL doesn't match what we expect + if (window.location.pathname !== expectedURL) { + setGroupPathInURL(currentGroupPath, false); + } + + document.body.innerHTML = ""; + document.head.title = "Tests"; + + Blaze.render(Template.testInBrowserBody, document.body); + + Tracker.flush(); + Tinytest._runTestsEverywhere(reportResults, function () { + running = false; + Meteor.onTestsComplete && Meteor.onTestsComplete(); + countDep.changed(); + Tracker.flush(); + + Meteor.connection._unsubscribeAll(); + }, currentGroupPath); + +}; + + +//// +//// Take incoming results and drive resultsTree +//// + +// report a series of events in a single test, or just the existence of +// that test if no events. this is the entry point for test results to +// this module. +var reportResults = function(results) { + var test = _findTestForResults(results); + + // Tolerate repeated reports: first undo the effect of any previous report + var status = _testStatus(test); + if (status === "failed") { + failedCount--; + countDep.changed(); + } else if (status === "succeeded") { + passedCount--; + countDep.changed(); + } + + // Now process the current report + if (Array.isArray(results.events)) { + // append events, if present + Array.prototype.push.apply((test.events || (test.events = [])), + results.events); + // sort and de-duplicate, based on sequence number + test.events.sort(function (a, b) { + return a.sequence - b.sequence; + }); + var out = []; + test.events.forEach(function (e) { + if (out.length === 0 || out[out.length - 1].sequence !== e.sequence) + out.push(e); + }); + test.events = out; + } + status = _testStatus(test); + if (status === "failed") { + failedCount++; + // Expand a failed test (but only set this if the user hasn't clicked on the + // test name yet). + if (test.expanded === undefined) + test.expanded = true; + if (!failedTests.includes(test.fullName)) + failedTests.push(test.fullName); + + countDep.changed(); + test.dep.changed(); + } else if (status === "succeeded") { + passedCount++; + countDep.changed(); + test.dep.changed(); + } else if (test.expanded) { + // re-render the test if new results come in and the test is + // currently expanded. + test.dep.changed(); + } +}; + +// forget all of the events for a particular test +var forgetEvents = function (results) { + var test = _findTestForResults(results); + var status = _testStatus(test); + if (status === "failed") { + failedCount--; + countDep.changed(); + } else if (status === "succeeded") { + passedCount--; + countDep.changed(); + } + delete test.events; + test.dep.changed(); +}; + +// given a 'results' as delivered via reportResults, find the +// corresponding leaf object in resultTree, creating one if it doesn't +// exist. it will be an object with attributes 'name', 'parent', and +// possibly 'events'. +var _findTestForResults = function (results) { + var groupPath = results.groupPath; // array + if ((! Array.isArray(groupPath)) || (groupPath.length < 1)) { + throw new Error("Test must be part of a group"); + } + + var group; + var i = 0; + groupPath.forEach(function(gname) { + var array = (group ? (group.groups || (group.groups = [])) + : resultTree); + var newGroup = array.find(function(g) { return g.name === gname; }); + if (! newGroup) { + newGroup = { + name: gname, + parent: (group || null), + path: groupPath.slice(0, i+1), + dep: new Tracker.Dependency + }; // create group + array.push(newGroup); + + if (group) + group.dep.changed(); + else + topLevelGroupsDep.changed(); + } + group = newGroup; + i++; + }); + + var testName = results.test; + var server = !!results.server; + var test = (group.tests || (group.tests = [])).find( + function(t) { return t.name === testName && + t.server === server; }); + if (! test) { + // create test + var nameParts = [...groupPath]; + nameParts.push(testName); + var fullName = nameParts.join(' - '); + test = { + name: testName, + parent: group, + server: server, + fullName: fullName, + dep: new Tracker.Dependency + }; + group.tests.push(test); + group.dep.changed(); + totalCount++; + countDep.changed(); + } + + return test; +}; + + + +//// +//// Helpers on test objects +//// + +var _testTime = function(t) { + if (t.events && t.events.length > 0) { + var lastEvent = t.events[t.events.length - 1]; + if (lastEvent.type === "finish") { + if ((typeof lastEvent.timeMs) === "number") { + return lastEvent.timeMs; + } + } + } + return null; +}; + +var _testStatus = function(t) { + var events = t.events || []; + if (events.find(function(x) { return x.type === "exception"; })) { + // "exception" should be last event, except race conditions on the + // server can make this not the case. Technically we can't tell + // if the test is still running at this point, but it can only + // result in FAIL. + return "failed"; + } else if (events.length == 0 || (events[events.length - 1].type != "finish")) { + return "running"; + } else if (events.some(function(e) { + return e.type == "fail" || e.type == "exception"; })) { + return "failed"; + } else { + return "succeeded"; + } +}; + + + +//// +//// Templates +//// + +//// Template - navBars + +Template.navBar.helpers({ + running: function() { + countDep.depend(); + return running; + }, + passed: function() { + countDep.depend(); + return failedCount === 0; + }, + total_test_time: function() { + countDep.depend(); + + // walk whole tree to get all tests + var walk = function (groups) { + var total = 0; + + (groups || []).forEach(function (group) { + (group.tests || []).forEach(function (t) { + total += _testTime(t); + }); + + total += walk(group.groups); + }); + + return total; + }; + + return walk(resultTree); + } +}); + + +//// Template - progressBar + +Template.progressBar.helpers({ + running: function () { + countDep.depend(); + return running; + }, + percentPass: function () { + countDep.depend(); + if (totalCount === 0) + return 0; + return 100*passedCount/totalCount; + }, + totalCount: function () { + countDep.depend(); + return totalCount; + }, + passedCount: function () { + countDep.depend(); + return passedCount; + }, + percentFail: function () { + countDep.depend(); + if (totalCount === 0) + return 0; + return 100*failedCount/totalCount; + }, + anyFail: function () { + countDep.depend(); + return failedCount > 0; + }, + barOuterClass: function () { + countDep.depend(); + return running ? 'progress-bar-animated progress-bar-striped' : ''; + }, + barInnerClass: function () { + countDep.depend(); + return (failedCount > 0 ? + 'bg-warning' : 'bg-success'); + } +}); + +//// Template - groupNav + +var changeToPath = function (path) { + // Update URL with new group path (pushState creates new history entry) + setGroupPathInURL(path, true); + + // Update session to trigger UI updates + Session.set("groupPath", path); + Session.set("rerunScheduled", true); + // pretend there's just been a hot code push + // so we run the tests completely fresh. + Reload._reload(); +}; + +Template.groupNav.helpers({ + groupPaths: function () { + var groupPath = Session.get("groupPath"); + var ret = []; + for (var i = 1; i <= groupPath.length; i++) { + ret.push({path: groupPath.slice(0,i), name: groupPath[i-1]}); + } + return ret; + }, + rerunScheduled: function () { + return Session.get("rerunScheduled"); + }, + isFiltered: function () { + var groupPath = Session.get("groupPath"); + return groupPath.length > 1 || groupPath[0] !== "tinytest"; + } +}); + +Template.groupNav.events({ + 'click .group': function () { + changeToPath(this.path); + }, + 'click .rerun': function () { + Session.set("rerunScheduled", true); + Reload._reload(); + }, + 'click .run-all': function () { + changeToPath(["tinytest"]); + } +}); + +Template.groupNav.onRendered(function () { + Tinytest._onCurrentClientTest = function (name) { + name = (name ? 'C: '+name : ''); + // Set the DOM directly so that it's immediate and we + // don't wait for Tracker to flush. + var span = document.getElementById('current-client-test'); + if (span) { + span.innerHTML = ''; + span.appendChild(document.createTextNode(name)); + } + }; +}); + +//// Template - uncaughtErrors + +Template.uncaughtErrors.helpers({ + uncaughtErrors() { + return Session.get("uncaughtErrors"); + } +}); + +//// Template - failedTests + +Template.failedTests.helpers({ + failedTests: function() { + countDep.depend(); + return failedTests; + } +}); + +//// Template - testTable + +Template.testTable.helpers({ + testdata: function () { + topLevelGroupsDep.depend(); + return resultTree; + }, + thisWithDep: function () { + this.dep.depend(); + return this; + } +}); + +//// Template - test_group + +Template.test_group.helpers({ + thisWithDep: function () { + this.dep.depend(); + return this; + } +}); + +Template.test_group.events({ + 'click .groupname': function (evt) { + changeToPath(this.path); + // prevent enclosing groups from also triggering on + // same groupname. It would be cleaner to think of + // this as each group only listening to its *own* + // groupname, but currently it listens to all of them. + evt.stopImmediatePropagation(); + } +}); + + +//// Template - test + +Template.test.helpers({ + test_status_display: function() { + var status = _testStatus(this); + if (status == "failed") { + return "FAIL"; + } else if (status == "succeeded") { + return "PASS"; + } else { + return "waiting..."; + } + }, + + test_time_display: function() { + var time = _testTime(this); + return (typeof time === "number") ? time + " ms" : ""; + }, + + test_class: function() { + var events = this.events || []; + var classes = [_testStatus(this)]; + + if (this.expanded) { + classes.push("expanded"); + } else { + classes.push("collapsed"); + } + + return classes.join(' '); + }, + + eventsArray: function() { + var events = this.events.filter(function(e) { + return e.type != "finish"; + }); + + var partitionBy = function(seq, func) { + var result = []; + var lastValue = {}; + seq.forEach(function(x) { + var newValue = func(x); + if (newValue === lastValue) { + result[result.length-1].push(x); + } else { + lastValue = newValue; + result.push([x]); + } + }); + return result; + }; + + var dupLists = partitionBy( + events.map(function(e) { + // XXX XXX We need something better than stringify! + // stringify([undefined]) === "[null]" + e = Object.assign({}, e); + delete e.sequence; + return {obj: e, str: JSON.stringify(e)}; + }), function(x) { return x.str; }); + + return dupLists.map(function(L) { + var obj = L[0].obj; + return (L.length > 1) ? Object.assign({times: L.length}, obj) : obj; + }); + } +}); + +Template.test.events({ + 'click .testname': function () { + this.expanded = ! this.expanded; + this.dep.changed(); + } +}); + + +//// Template - event + +Template.event.events({ + 'click .debug': function () { + // the way we manage groupPath, shortName, cookies, etc, is really + // messy. needs to be aggressively refactored. + forgetEvents({groupPath: this.cookie.groupPath, + test: this.cookie.shortName}); + Tinytest._debugTest(this.cookie, reportResults); + } +}); + +// e.g. doDiff('abc', 'bcd') => [[-1, 'a'], [0, 'bc'], [1, 'd']] +var doDiff = function (str1, str2) { + const diff = diffChars(str1, str2); + + return diff.map(part => { + if (part.added) return [1, part.value]; + if (part.removed) return [-1, part.value]; + return [0, part.value]; + }); +}; + +Template.event.helpers({ + get_details: function() { + + var details = this.details; + + if (! details) { + return null; + } else { + + var type = details.type; + var stack = details.stack; + + details = Array.isArray(details) && [...details] || Object.assign({}, details); + delete details.type; + delete details.stack; + + var prepare = function(details) { + if (type === 'string_equal') { + var diff = doDiff(details.actual, + details.expected); + } + + return Object.entries(details).map(function([key, val]) { + + // make test._stringEqual results print nicely, + // in particular for multiline strings + if (type === 'string_equal' && + (key === 'actual' || key === 'expected')) { + var html = '
';
+            diff.forEach(function (piece) {
+              var which = piece[0];
+              var text = piece[1];
+              if (which === 0 ||
+                which === (key === 'actual' ? -1 : 1)) {
+                var htmlBit = Blaze._escape(text).replace(
+                  /\n/g, '
'); + if (which !== 0) + htmlBit = '' + htmlBit + ''; + html += htmlBit; + } + }); + html += '
'; + val = new Spacebars.SafeString(html); + } + + // You can end up with a an undefined value, e.g. using + // isNull without providing a message attribute: isNull(1). + // No need to display those. + if (typeof val !== 'undefined') { + return { + key: key, + val: val + }; + } else { + return undefined; + } + }).filter(Boolean); + }; + + return { + type: type, + stack: stack, + details: prepare(details) + }; + } + }, + + is_debuggable: function() { + return !!this.cookie; + } +}); \ No newline at end of file diff --git a/packages/test-in-browser/package.js b/packages/test-in-browser/package.js new file mode 100644 index 000000000..f6292e46c --- /dev/null +++ b/packages/test-in-browser/package.js @@ -0,0 +1,42 @@ +Package.describe({ + summary: "Run tests interactively in the browser", + version: '1.5.0', + documentation: null +}); + +Npm.depends({ + 'bootstrap': '5.3.8', + 'diff': '8.0.2' +}); + +Package.onUse(function (api) { + api.use('ecmascript'); + // XXX this should go away, and there should be a clean interface + // that tinytest and the driver both implement? + + if (process.env.TESTS_USE_JQUERY) { + api.use('jquery@3.0.0', 'client'); + } + api.use('tinytest'); + api.use('session'); + api.use('reload'); + api.use([ + 'webapp', + 'blaze', + 'templating', + 'spacebars', + 'ddp', + 'tracker', + ], 'client'); + + api.addFiles([ + 'driver.html', + 'driver.js', + 'driver.css', + ], "client"); + + api.use("random", "server"); + api.mainModule("server.js", "server"); + + api.export('runTests'); +}); \ No newline at end of file diff --git a/packages/test-in-browser/server.js b/packages/test-in-browser/server.js new file mode 100644 index 000000000..1b7ef60ae --- /dev/null +++ b/packages/test-in-browser/server.js @@ -0,0 +1,6 @@ +// If autoupdate is installed, modifying Meteor.settings.public will cause +// the hashes in Autoupdate.versions to be computed differently, which +// will trigger a reload in the browser, which is what we want when +// running test-packages in the browser, since reloading the window is +// what kicks off both server and client tests again. +Meteor.settings.public.autoupdateSalt = Random.id(); \ No newline at end of file From a559d9d85dd044330a8d509f7dc011c49c22c6f0 Mon Sep 17 00:00:00 2001 From: jankapunkt Date: Wed, 8 Apr 2026 09:24:33 +0200 Subject: [PATCH 09/16] tests: full dual mode support (jquery on/off) for local browser-based tests --- .github/workflows/blaze-tests.yml | 2 +- packages/blaze/dombackend.js | 12 +-- packages/blaze/package.js | 6 +- .../spacebars-tests/old_templates_tests.js | 56 ++++++---- packages/spacebars-tests/template_tests.js | 102 +++++++++--------- packages/spacebars-tests/templating_tests.js | 74 +++++++++---- 6 files changed, 151 insertions(+), 101 deletions(-) diff --git a/.github/workflows/blaze-tests.yml b/.github/workflows/blaze-tests.yml index 0dd1c3024..3c54c51a7 100644 --- a/.github/workflows/blaze-tests.yml +++ b/.github/workflows/blaze-tests.yml @@ -79,4 +79,4 @@ jobs: pkill -TERM -P $(cat /tmp/meteor_test_pid) fi shell: bash - working-directory: test-app \ No newline at end of file + working-directory: test-app diff --git a/packages/blaze/dombackend.js b/packages/blaze/dombackend.js index 0ba695760..54e6a8789 100644 --- a/packages/blaze/dombackend.js +++ b/packages/blaze/dombackend.js @@ -4,14 +4,6 @@ Blaze._DOMBackend = DOMBackend; let $jq; let $jqSource; -try { - const p = require.resolve('jquery'); - if (typeof p === 'string') { - $jq = require('jquery'); - $jqSource = `npm dependency (${p})`; - } -} catch {} - if (!$jq && typeof jQuery !== 'undefined') { $jq = jQuery; $jqSource = 'global scope'; @@ -24,9 +16,9 @@ if (!$jq && typeof Package !== 'undefined' && Package.jquery) { const _hasJQuery = !!$jq; if (_hasJQuery && typeof console !== 'undefined') { - const version = jQuery.fn?.jquery ?? ' '; + const version = $jq.fn?.jquery ?? ' '; console.info( - `[Blaze] jQuery${version} detected as DOM backend. Native DOM backend is available — ` + + `[Blaze] jQuery ${version} detected as DOM backend. Native DOM backend is available — ` + 'remove jquery to enable native DOM backend. jQuery support will be removed in Blaze 4.0.' ); console.info( diff --git a/packages/blaze/package.js b/packages/blaze/package.js index bd1bf8207..fb92a6545 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -12,7 +12,7 @@ Package.onUse(function (api) { api.use('reactive-var@1.0.12'); api.use('ordered-dict@1.2.0'); api.use('ecmascript@0.16.9'); - api.use('jquery@3.0.0', { weak: true }); + api.use('jquery@3.0.0', 'client', { weak: true }); api.export([ 'Blaze', 'UI', @@ -55,10 +55,6 @@ Package.onTest(function (api) { api.use('reactive-var@1.0.12'); api.use('tracker@1.3.2'); - if (process.env.TEST_USE_JQUERY) { - api.use('jquery@3.0.0'); - } - api.use('blaze'); api.use('blaze-tools@2.0.0'); // for BlazeTools.toJS api.use('html-tools@2.0.0'); diff --git a/packages/spacebars-tests/old_templates_tests.js b/packages/spacebars-tests/old_templates_tests.js index b63227d60..99d0a5b78 100644 --- a/packages/spacebars-tests/old_templates_tests.js +++ b/packages/spacebars-tests/old_templates_tests.js @@ -107,7 +107,7 @@ Tinytest.add( }; const div = renderToDiv(tmpl); - test.equal($(div).find('div')[0].className, 'aaa124zzz'); + test.equal(div.querySelector('div').className, 'aaa124zzz'); } ); @@ -126,7 +126,9 @@ Tinytest.add( }; const div = renderToDiv(tmpl); - const span = $(div).find('span')[0]; + const span = hasJquery + ? $(div).find('span')[0] + : div.querySelector('span'); test.equal(span.innerHTML, 'hi'); test.isTrue(span.hasAttribute('selected')); test.equal(span.getAttribute('x'), 'X'); @@ -153,7 +155,9 @@ Tinytest.add( }; let div = renderToDiv(tmpl); - let elems = $(div).find('> *'); + let elems = hasJquery + ? $(div).find('> *') + : div.querySelectorAll(':scope > *'); test.equal(elems.length, 1); test.equal(elems[0].nodeName, 'SPAN'); let span = elems[0]; @@ -162,13 +166,17 @@ Tinytest.add( R.set('asdf'); Tracker.flush(); - elems = $(div).find('> *'); + elems = hasJquery + ? $(div).find('> *') + : div.querySelectorAll(':scope > *'); test.equal(elems.length, 0); test.equal(canonicalizeHtml(div.innerHTML), 'asdf'); R.set('blah'); Tracker.flush(); - elems = $(div).find('> *'); + elems = hasJquery + ? $(div).find('> *') + : div.querySelectorAll(':scope > *'); test.equal(elems.length, 1); test.equal(elems[0].nodeName, 'SPAN'); span = elems[0]; @@ -702,7 +710,9 @@ Tinytest.addAsync( }; const div = renderToDiv(tmpl); - const selectEl = $(div).find('select')[0]; + const selectEl = hasJquery + ? $(div).find('select')[0] + : div.querySelector('select'); // returns canonicalized contents of `div` in the form eg // [""]. strip out selected attributes -- we @@ -751,8 +761,8 @@ Tinytest.addAsync( '', ]); test.equal(selectEl.value, 'value2'); - test.equal($(selectEl).find('option')[0].selected, false); - test.equal($(selectEl).find('option')[1].selected, true); + test.equal(selectEl.querySelectorAll('option')[0].selected, false); + test.equal(selectEl.querySelectorAll('option')[1].selected, true); // swap selection await options.updateAsync({ value: 'value1' }, { $set: { selected: true } }); @@ -770,8 +780,8 @@ Tinytest.addAsync( '', ]); test.equal(selectEl.value, 'value1'); - test.equal($(selectEl).find('option')[0].selected, true); - test.equal($(selectEl).find('option')[1].selected, false); + test.equal(selectEl.querySelectorAll('option')[0].selected, true); + test.equal(selectEl.querySelectorAll('option')[1].selected, false); // change value and label await options.updateAsync({ value: 'value1' }, { $set: { value: 'value1.0' } }); @@ -789,8 +799,8 @@ Tinytest.addAsync( '', ]); test.equal(selectEl.value, 'value1.0'); - test.equal($(selectEl).find('option')[0].selected, true); - test.equal($(selectEl).find('option')[1].selected, false); + test.equal(selectEl.querySelectorAll('option')[0].selected, true); + test.equal(selectEl.querySelectorAll('option')[1].selected, false); // unselect and then select both options. normally, the second is // selected (since it got selected later). then switch to ", ""]. strip out selected attributes -- we @@ -867,8 +867,8 @@ Tinytest.addAsync('spacebars-tests - template_tests - select tags', async functi '', ]); test.equal(selectEl.value, 'value2'); - test.equal($(selectEl).find('option')[0].selected, false); - test.equal($(selectEl).find('option')[1].selected, true); + test.equal(selectEl.querySelectorAll('option')[0].selected, false); + test.equal(selectEl.querySelectorAll('option')[1].selected, true); // swap selection await options.updateAsync({ value: 'value2' }, { $set: { selected: false } }); @@ -886,8 +886,8 @@ Tinytest.addAsync('spacebars-tests - template_tests - select tags', async functi '', ]); test.equal(selectEl.value, 'value1'); - test.equal($(selectEl).find('option')[0].selected, true); - test.equal($(selectEl).find('option')[1].selected, false); + test.equal(selectEl.querySelectorAll('option')[0].selected, true); + test.equal(selectEl.querySelectorAll('option')[1].selected, false); // change value and label await options.updateAsync({ value: 'value1' }, { $set: { value: 'value1.0' } }); @@ -905,8 +905,8 @@ Tinytest.addAsync('spacebars-tests - template_tests - select tags', async functi '', ]); test.equal(selectEl.value, 'value1.0'); - test.equal($(selectEl).find('option')[0].selected, true); - test.equal($(selectEl).find('option')[1].selected, false); + test.equal(selectEl.querySelectorAll('option')[0].selected, true); + test.equal(selectEl.querySelectorAll('option')[1].selected, false); // unselect and then select both options. normally, the second is // selected (since it got selected later). then switch to