From 3a8b6925b50f5b562fb22938dbaa72738c2d5589 Mon Sep 17 00:00:00 2001 From: italo jose Date: Fri, 3 Apr 2026 16:20:22 -0300 Subject: [PATCH 1/7] test: add comprehensive unit tests for facts-base, mongo-decimal EJSON integration, and xmlbuilder, while cleaning up bundle-visualizer code. --- packages/facts-base/facts_base.tests.js | 71 ++++++++++++++++ packages/non-core/bundle-visualizer/client.js | 17 ++-- packages/non-core/bundle-visualizer/server.js | 6 +- .../non-core/bundle-visualizer/sunburst.js | 8 +- .../non-core/mongo-decimal/decimal_tests.js | 82 +++++++++++++++++-- packages/non-core/mongo-decimal/package.js | 1 + packages/non-core/xmlbuilder/package.js | 5 ++ .../non-core/xmlbuilder/xmlbuilder_tests.js | 50 +++++++++++ 8 files changed, 211 insertions(+), 29 deletions(-) create mode 100644 packages/non-core/xmlbuilder/xmlbuilder_tests.js diff --git a/packages/facts-base/facts_base.tests.js b/packages/facts-base/facts_base.tests.js index 9b15989889..729a82b6df 100644 --- a/packages/facts-base/facts_base.tests.js +++ b/packages/facts-base/facts_base.tests.js @@ -171,3 +171,74 @@ Tinytest.add("facts-base - setUserIdFilter replaces the filter", (test) => { return !!Package.autopublish; }); }); + +// -- resetServerFacts with active subscriptions -- + +Tinytest.add("facts-base - resetServerFacts does not notify subscriptions", (test) => { + Facts.resetServerFacts(); + const sub = mockSub(); + Facts._setActiveSubscriptions([sub]); + + Facts.incrementServerFact("pkg", "val", 5); + Facts.resetServerFacts(); + + // added was called once for the increment, but reset should not trigger notifications + test.equal(sub.calls.added.length, 1); + test.equal(sub.calls.changed.length, 0); + test.equal(Facts._factsByPackage, {}); + + Facts._setActiveSubscriptions([]); +}); + +// -- incrementServerFact: zero increment -- + +Tinytest.add("facts-base - incrementServerFact with zero increment", (test) => { + Facts.resetServerFacts(); + Facts.incrementServerFact("pkg", "counter", 0); + + test.equal(Facts._factsByPackage["pkg"].counter, 0); +}); + +// -- incrementServerFact: multiple facts same package -- + +Tinytest.add("facts-base - incrementServerFact supports many facts per package", (test) => { + Facts.resetServerFacts(); + + Facts.incrementServerFact("multi", "a", 1); + Facts.incrementServerFact("multi", "b", 2); + Facts.incrementServerFact("multi", "c", 3); + + test.equal(Facts._factsByPackage["multi"], { a: 1, b: 2, c: 3 }); +}); + +// -- subscription: changed sends only the changed field -- + +Tinytest.add("facts-base - changed notification only includes the updated field", (test) => { + Facts.resetServerFacts(); + const sub = mockSub(); + + Facts.incrementServerFact("pkg", "a", 1); + Facts.incrementServerFact("pkg", "b", 2); + Facts._setActiveSubscriptions([sub]); + + Facts.incrementServerFact("pkg", "a", 10); + + test.equal(sub.calls.changed.length, 1); + test.equal(sub.calls.changed[0].fields, { a: 11 }); + + Facts._setActiveSubscriptions([]); +}); + +// -- subscription added uses correct collection name -- + +Tinytest.add("facts-base - subscription added uses 'meteor_Facts_server' collection", (test) => { + Facts.resetServerFacts(); + const sub = mockSub(); + Facts._setActiveSubscriptions([sub]); + + Facts.incrementServerFact("test-pkg", "val", 1); + + test.equal(sub.calls.added[0].collection, "meteor_Facts_server"); + + Facts._setActiveSubscriptions([]); +}); diff --git a/packages/non-core/bundle-visualizer/client.js b/packages/non-core/bundle-visualizer/client.js index b96adc5443..3b292b9ca5 100644 --- a/packages/non-core/bundle-visualizer/client.js +++ b/packages/non-core/bundle-visualizer/client.js @@ -20,22 +20,15 @@ async function main(builder) { // Always match the protocol (http or https) and the domain:port of the // current page. - const url = [ - "//" + - location.host + - methodNameStats + - "?cacheBuster=" + - Math.random().toString(36).slice(2) - ].join(); + const url = `//${location.host}${methodNameStats}?cacheBuster=${Math.random().toString(36).slice(2)}`; try { const data = await fetch(url, { method: "GET" }); new builder({ container }).loadJson(await data.json()) - } catch (err) { - console.error([ - packageName + ": Couldn't load stats for visualization.", - "Are you using standard-minifier-js >= 2.1.0 as the minifier?", - ].join(" ")) + } catch { + console.error( + `${packageName}: Couldn't load stats for visualization. Are you using standard-minifier-js >= 2.1.0 as the minifier?` + ) } } diff --git a/packages/non-core/bundle-visualizer/server.js b/packages/non-core/bundle-visualizer/server.js index 626a98d67c..3cf906753e 100644 --- a/packages/non-core/bundle-visualizer/server.js +++ b/packages/non-core/bundle-visualizer/server.js @@ -36,7 +36,7 @@ function getStatBundles() { function readOrNull(file) { try { return JSON.parse(fsReadFileSync(file, "utf8")); - } catch (err) { + } catch { return null; } } @@ -115,7 +115,7 @@ function d3TreeFromStats(stats) { .map(name => sizeOrDetail(name // Change the "packages/bundle.js" name to "(bundle)" - .replace(/^[^\/]+\/(.*)\.js$/, "($1)"), + .replace(/^[^/]+\/(.*)\.js$/, "($1)"), stats.minifiedBytesByPackage[name])); } @@ -152,7 +152,7 @@ function statsMiddleware(request, response) { sendJSON({ name: "main", - children: statBundles.map((statBundle, index, array) => ({ + children: statBundles.map((statBundle) => ({ name: statBundle.arch, type: typeBundle, children: d3TreeFromStats(statBundle.stats), diff --git a/packages/non-core/bundle-visualizer/sunburst.js b/packages/non-core/bundle-visualizer/sunburst.js index 906eeee869..c7f72073a8 100644 --- a/packages/non-core/bundle-visualizer/sunburst.js +++ b/packages/non-core/bundle-visualizer/sunburst.js @@ -41,8 +41,6 @@ import { prefixedClass, } from "./common.js"; -import * as classes from "./classNames.js"; - // Dimensions of sunburst. const width = 950; const height = 600; @@ -182,7 +180,7 @@ export class Sunburst { }); } - draw(json, i) { + draw(json) { const svg = this.elements.chart .append("svg:svg") .attr("width", width) @@ -284,7 +282,7 @@ export class Sunburst { // Restore everything to full opacity when moving off the visualization. mouseleaveEvent() { const self = this; - return self.mouseleave || (self.mouseleave = function (d) { + return self.mouseleave || (self.mouseleave = function (_d) { // Hide the breadcrumb trail self.elements.trail .style("visibility", "hidden"); @@ -307,7 +305,7 @@ export class Sunburst { } // Update the breadcrumb trail to show the current sequence and percentage. - updateBreadcrumbs(nodeArray, percentageString) { + updateBreadcrumbs(nodeArray) { // Data join; key function combines name and depth (= position in sequence). const trail = this.elements.trail .selectAll("div") diff --git a/packages/non-core/mongo-decimal/decimal_tests.js b/packages/non-core/mongo-decimal/decimal_tests.js index b4723235fc..e88932ae39 100644 --- a/packages/non-core/mongo-decimal/decimal_tests.js +++ b/packages/non-core/mongo-decimal/decimal_tests.js @@ -1,16 +1,80 @@ -Tinytest.addAsync("mongo-decimal - insert/find Decimal", async function (test) { +// -- EJSON integration -- + +Tinytest.add("mongo-decimal - typeName returns 'Decimal'", (test) => { + const d = Decimal("1.5"); + test.equal(d.typeName(), "Decimal"); +}); + +Tinytest.add("mongo-decimal - toJSONValue returns string representation", (test) => { + const d = Decimal("3.141592653589793"); + const json = d.toJSONValue(); + test.equal(typeof json, "string"); + test.equal(json, "3.141592653589793"); +}); + +Tinytest.add("mongo-decimal - clone produces equal but independent copy", (test) => { + const original = Decimal("99.99"); + const copy = original.clone(); + + test.equal(copy.toString(), original.toString()); + // They must be different object instances + test.isFalse(copy === original); +}); + +Tinytest.add("mongo-decimal - EJSON.stringify and EJSON.parse round-trip", (test) => { + const d = Decimal("2.718281828459045"); + const str = EJSON.stringify({ value: d }); + const parsed = EJSON.parse(str); + + test.equal(parsed.value.toString(), d.toString()); + test.instanceOf(parsed.value, Decimal); +}); + +Tinytest.add("mongo-decimal - EJSON.clone preserves Decimal", (test) => { + const d = Decimal("42"); + const cloned = EJSON.clone(d); + + test.equal(cloned.toString(), "42"); + test.isFalse(cloned === d); + test.instanceOf(cloned, Decimal); +}); + +Tinytest.add("mongo-decimal - Decimal handles zero correctly", (test) => { + const d = Decimal("0"); + test.equal(d.toString(), "0"); + test.equal(d.toJSONValue(), "0"); + test.equal(d.typeName(), "Decimal"); +}); + +Tinytest.add("mongo-decimal - Decimal handles negative numbers", (test) => { + const d = Decimal("-123.456"); + test.equal(d.toString(), "-123.456"); + + const json = EJSON.stringify({ n: d }); + const parsed = EJSON.parse(json); + test.equal(parsed.n.toString(), "-123.456"); +}); + +Tinytest.add("mongo-decimal - Decimal handles very large numbers", (test) => { + const big = "9999999999999999999999999999999999.9999"; + const d = Decimal(big); + // Verify round-trip through EJSON preserves value + const str = EJSON.stringify({ v: d }); + const parsed = EJSON.parse(str); + test.equal(parsed.v.toString(), d.toString()); +}); + +// -- MongoDB insert/find (server only) -- + +Tinytest.addAsync("mongo-decimal - insert/find Decimal", async (test) => { // TODO [fibers]: this should work on the client as well. - // it looks like we should insert just in the minimongo and then test, - // but right now the coll.insertAsync is finishing when the server side finishes - // meaning the data on the client side is no longer there. Maybe the idea of accept callbacks - // on the new Async methods could solve these issues. if (Meteor.isClient) return; - var coll = new Mongo.Collection("mongo-decimal"); - var pi = Decimal("3.141592653589793"); + const coll = new Mongo.Collection("mongo-decimal"); + const pi = Decimal("3.141592653589793"); - await coll.insertAsync({ pi: pi }); - var found = await coll.findOneAsync({ pi: pi }); + await coll.insertAsync({ pi }); + const found = await coll.findOneAsync({ pi }); test.equal(found.pi, pi); }); diff --git a/packages/non-core/mongo-decimal/package.js b/packages/non-core/mongo-decimal/package.js index e587275fb6..d7a1b63bd4 100644 --- a/packages/non-core/mongo-decimal/package.js +++ b/packages/non-core/mongo-decimal/package.js @@ -17,6 +17,7 @@ Package.onUse(function (api) { Package.onTest(function (api) { api.use('mongo'); api.use('mongo-decimal'); + api.use('ejson'); api.use('insecure'); api.use(['tinytest']); api.addFiles('decimal_tests.js', ['client', 'server']); diff --git a/packages/non-core/xmlbuilder/package.js b/packages/non-core/xmlbuilder/package.js index 305539606e..beea822a7c 100644 --- a/packages/non-core/xmlbuilder/package.js +++ b/packages/non-core/xmlbuilder/package.js @@ -14,3 +14,8 @@ Package.onUse(function (api) { api.export('XmlBuilder', 'server'); }); + +Package.onTest(function (api) { + api.use(['tinytest', 'xmlbuilder']); + api.addFiles('xmlbuilder_tests.js', 'server'); +}); diff --git a/packages/non-core/xmlbuilder/xmlbuilder_tests.js b/packages/non-core/xmlbuilder/xmlbuilder_tests.js new file mode 100644 index 0000000000..cd54081fa9 --- /dev/null +++ b/packages/non-core/xmlbuilder/xmlbuilder_tests.js @@ -0,0 +1,50 @@ +Tinytest.add("xmlbuilder - XmlBuilder is exported and defined", (test) => { + test.isTrue(typeof XmlBuilder === "object" || typeof XmlBuilder === "function"); +}); + +Tinytest.add("xmlbuilder - create returns an object with methods", (test) => { + const root = XmlBuilder.create("root"); + test.isTrue(typeof root === "object"); + test.isTrue(typeof root.end === "function"); +}); + +Tinytest.add("xmlbuilder - simple element generation", (test) => { + const xml = XmlBuilder.create("root") + .ele("child", "hello") + .end({ pretty: false }); + + test.matches(xml, //); + test.matches(xml, /hello<\/child>/); + test.matches(xml, /<\/root>/); +}); + +Tinytest.add("xmlbuilder - element with attributes", (test) => { + const xml = XmlBuilder.create("item") + .att("id", "42") + .att("type", "test") + .end({ pretty: false }); + + test.matches(xml, /id="42"/); + test.matches(xml, /type="test"/); +}); + +Tinytest.add("xmlbuilder - nested elements", (test) => { + const xml = XmlBuilder.create("parent") + .ele("child") + .ele("grandchild", "value") + .end({ pretty: false }); + + test.matches(xml, //); + test.matches(xml, //); + test.matches(xml, /value<\/grandchild>/); +}); + +Tinytest.add("xmlbuilder - XML declaration is included by default", (test) => { + const xml = XmlBuilder.create("root").end(); + test.matches(xml, /^<\?xml version="1\.0"\?>/); +}); + +Tinytest.add("xmlbuilder - empty element", (test) => { + const xml = XmlBuilder.create("empty").end({ pretty: false }); + test.matches(xml, //); +}); From d7f3af5f8809c4172b21772b097b3509257c7b0b Mon Sep 17 00:00:00 2001 From: italo jose Date: Fri, 3 Apr 2026 16:32:10 -0300 Subject: [PATCH 2/7] chore: include additional packages in .fmtignore and .oxlintignore configurations --- .fmtignore | 4 ++++ .oxlintignore | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.fmtignore b/.fmtignore index e28e2bf874..fe089bcddd 100644 --- a/.fmtignore +++ b/.fmtignore @@ -31,3 +31,7 @@ packages/**/.npm/ packages/* !packages/facts-base/ +!packages/facts-ui/ +!packages/non-core/bundle-visualizer/ +!packages/non-core/mongo-decimal/ +!packages/non-core/xmlbuilder/ diff --git a/.oxlintignore b/.oxlintignore index 164954f012..31fd4c2bcb 100644 --- a/.oxlintignore +++ b/.oxlintignore @@ -28,3 +28,7 @@ packages/**/.npm/ packages/* !packages/facts-base/ +!packages/facts-ui/ +!packages/non-core/bundle-visualizer/ +!packages/non-core/mongo-decimal/ +!packages/non-core/xmlbuilder/ From 73ae0f3510cbe1a0c11c234632011570df0b47bf Mon Sep 17 00:00:00 2001 From: italo jose Date: Fri, 3 Apr 2026 16:32:52 -0300 Subject: [PATCH 3/7] refactor: standardize code style and formatting in facts-ui package --- packages/facts-ui/facts_ui.html | 19 ++++++++++--------- packages/facts-ui/facts_ui_client.js | 7 +++---- packages/facts-ui/package.js | 17 ++++++----------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/packages/facts-ui/facts_ui.html b/packages/facts-ui/facts_ui.html index b41b7ae6fe..7652bd268f 100644 --- a/packages/facts-ui/facts_ui.html +++ b/packages/facts-ui/facts_ui.html @@ -1,14 +1,15 @@ -