Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7f1f234
tests: update test-app to 3.4
jankapunkt Mar 29, 2026
1cd9cac
tests: update npm deps to latest
jankapunkt Mar 29, 2026
3a9c783
fix(blaze): explicitly catch and report exceptions in then(maybePromi…
jankapunkt Mar 29, 2026
ce83d37
fix(tests): move async tests to Tinytest.addAsync
jankapunkt Mar 30, 2026
470ef7c
tests: fully remove jquery from all tests and fix delegation
jankapunkt Mar 30, 2026
1a43798
tests: include jquery based on env flag in tests
jankapunkt Mar 31, 2026
4a71b4f
Bump lodash from 4.17.21 to 4.18.1 in /site (#498)
dependabot[bot] Apr 3, 2026
b9d0be4
Merge remote-tracking branch 'origin' into tests/jquery
jankapunkt Apr 4, 2026
c15a1de
Merge remote-tracking branch 'origin/release-3.1.0' into tests/jquery
jankapunkt Apr 4, 2026
d0c18d1
packages: add modified test-in-browser package for on/off jQuery in t…
jankapunkt Apr 8, 2026
a559d9d
tests: full dual mode support (jquery on/off) for local browser-based…
jankapunkt Apr 8, 2026
48de9aa
ci: run tests with and without jquery
jankapunkt Apr 8, 2026
8da1ef4
tests: full jquery on/off compat via --extra-packages
jankapunkt Apr 8, 2026
cbe9280
ci: correct typo in .github/workflows/blaze-tests.yml
jankapunkt Apr 8, 2026
731117d
fix(blaze): delegate teardown in HTMLElement.remove
jankapunkt Apr 8, 2026
5339ffa
Merge remote-tracking branch 'origin/release-3.1.0' into tests/jquery
jankapunkt Apr 10, 2026
79a5299
fix: address review comments
jankapunkt Apr 11, 2026
6c20849
fix: revert error handling in materializer
jankapunkt Apr 11, 2026
46edae1
fix(tests): explicti manual focus/blur test
jankapunkt Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion .github/workflows/blaze-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,80 @@ jobs:
pkill -TERM -P $(cat /tmp/meteor_test_pid)
fi
shell: bash
working-directory: test-app
working-directory: test-app

# this re-runs the app tests with the test-in-console driver
# but in this case we include jQuery via --extra-packages=jquery,
# which causes the tests to run with jQuery back-compatibility mode.
# This should be removed, once we fully dropped jQuery support in Blaze 4.x
test-app-jquery:
needs: test-app
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive

- name: Setup Node.js environment
uses: actions/setup-node@v6
with:
node-version: 22

- name: Install Meteor
run: |
npx meteor@latest
echo "$HOME/.meteor" >> $GITHUB_PATH

- name: Link packages directory
run: ln -sfn ../packages ./packages
working-directory: test-app

- name: Meteor npm install
run: meteor npm install
working-directory: test-app

- name: Start meteor test-packages (background)
run: |
export URL='http://localhost:4096/'
meteor test-packages --driver-package test-in-console -p 4096 --extra-packages=jquery --exclude "${TEST_PACKAGES_EXCLUDE:-}" > /tmp/meteor_test_output.log 2>&1 &
echo $! > /tmp/meteor_test_pid
working-directory: test-app

# Wait for test-in-console to be ready for up to 6 minutes
# in order to accommodate slower CI environments
- name: Wait for test-in-console to be ready
run: |

for i in {1..360}; do
if grep -q 'test-in-console listening' /tmp/meteor_test_output.log; then
echo "test-in-console is ready."
break
fi
echo "Waiting for test-in-console... attempt $i"
sleep 1
done
# Fail if the service didn't start
if ! grep -q 'test-in-console listening' /tmp/meteor_test_output.log; then
echo "test-in-console did not start in time."
cat /tmp/meteor_test_output.log # Print the log for debugging
exit 1
fi
shell: bash
working-directory: test-app

- name: Run puppeteerRunner.js
run: meteor node puppeteerRunner.js
env:
URL: http://localhost:4096/
working-directory: test-app

- name: Kill meteor test-packages process
if: always()
run: |
if [ -f /tmp/meteor_test_pid ]; then
pkill -TERM -P $(cat /tmp/meteor_test_pid)
fi
shell: bash
working-directory: test-app
1 change: 0 additions & 1 deletion packages/blaze/.versions
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions packages/blaze/blaze.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as $ from 'jquery';

import { Tracker } from 'meteor/tracker';
import { Meteor } from 'meteor/meteor';
declare module 'meteor/blaze' {
Expand Down
107 changes: 72 additions & 35 deletions packages/blaze/dombackend.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
const DOMBackend = {};
Blaze._DOMBackend = DOMBackend;

const $jq = (typeof jQuery !== 'undefined' ? jQuery :
(typeof Package !== 'undefined' && Package.jquery ?
(Package.jquery.jQuery || Package.jquery.$) : null));
let $jq;
let $jqSource;

const _hasJQuery = !!$jq;
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 = $jq.fn?.jquery ?? ' ';
console.info(
`[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 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 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
Expand All @@ -37,7 +48,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);
};
Expand All @@ -63,19 +74,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());
Expand Down Expand Up @@ -123,20 +122,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);
Expand All @@ -158,6 +144,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.

Expand Down Expand Up @@ -274,6 +300,17 @@ 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 full descendant subtree.
(function(removeFn) {
HTMLElement.prototype.remove = function () {
DOMBackend.Teardown.tearDownElement(this);
return removeFn.apply(this, arguments);
};
})(HTMLElement.prototype.remove);

}


Expand Down
5 changes: 1 addition & 4 deletions packages/blaze/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ 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');
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', 'client', { weak: true });
api.export([
'Blaze',
'UI',
Expand Down Expand Up @@ -53,8 +52,6 @@ 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('reactive-var@1.0.12');
api.use('tracker@1.3.2');

Expand Down
26 changes: 14 additions & 12 deletions packages/blaze/render_tests.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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), "<p>Hello</p>");

Expand All @@ -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);

Expand Down Expand Up @@ -325,7 +325,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) {
test.equal(canonicalizeHtml(div.innerHTML), '<span class="blah"></span>');
test.equal(R._numListeners(), 1);

$(div).remove();
if (hasJquery) { $(div).remove() } else { div.remove() }

test.equal(R._numListeners(), 0);
})();
Expand Down Expand Up @@ -354,7 +354,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) {
Tracker.flush();
test.equal(canonicalizeHtml(div.innerHTML), '<div style="background-color: red; padding: 10px"></div>');

$(div).remove();
if (hasJquery) { $(div).remove() } else { div.remove() }

test.equal(style._numListeners(), 0);
})();
Expand Down Expand Up @@ -393,7 +393,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) {
test.equal(canonicalizeHtml(div.innerHTML), '<span style="jquery-style: hidden"></span>');
test.equal(R._numListeners(), 1);

$(div).remove();
if (hasJquery) { $(div).remove() } else { div.remove() }

test.equal(R._numListeners(), 0);
})();
Expand Down Expand Up @@ -482,7 +482,7 @@ Tinytest.add("blaze - render - reactive attributes", function (test) {
Tracker.flush();
test.equal(canonicalizeHtml(div.innerHTML), '<span ggg="" id="foo"></span>');

$(div).remove();
if (hasJquery) { $(div).remove() } else { div.remove() }

test.equal(R._numListeners(), 0);
})();
Expand Down Expand Up @@ -574,7 +574,7 @@ Tinytest.add("blaze - render - templates and views", function (test) {
test.equal(canonicalizeHtml(div.innerHTML), '123<hr>');

buf.length = 0;
$(div).remove();
if (hasJquery) { $(div).remove() } else { div.remove() }
buf.sort();
test.equal(buf, ['destroyed 1', 'destroyed 2', 'destroyed 3']);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
Loading