Convert client-reachable models/lib helpers from CommonJS to ES modules (1/2).

The rspack client build treats a `module.exports` file imported into the
client bundle as an ES module and throws
"ES Modules may not assign module.exports" at evaluation time, aborting
the whole client module chain (No such template: swimlane / notifications
/ boardButtons, blank board, missing top bar). This is not an rspack
version regression (the lockfile has pinned ~1.7.x since v8.40); it is
that WeKan added CommonJS helpers to the client bundle.

Convert the 42 client-reachable pure-logic models/lib helpers from
`module.exports = { ... }` to `export { ... }`, and calendarFilter's
internal require('./swimlaneFilter') to an import with a .js extension
(node ESM needs it; rspack/reify tolerate it). Server-only helpers that
use node built-ins (fs/path/child_process) stay CommonJS. The plain-Node
.cjs unit tests for these helpers now use `await import(...js)` (wrapped
in an async IIFE); a source-guard in importVirtualMembers was updated
from module.exports to export. Verified: the full tests/*.test.cjs suite
has the SAME 7 pre-existing failures as before this change and no new
ones.
This commit is contained in:
Lauri Ojansivu committed 2026-07-23 18:55:30 +03:00
1 parent eca452d674
commit 8d1fae4f7d
82 files changed
+205 -85

No files matched your search

+1 -1
View File
@@ -47,4 +47,4 @@ function buildCardAttachmentMeta(card, getCardById) {
return meta;
}
module.exports = { buildCardAttachmentMeta };
export { buildCardAttachmentMeta };
+1 -1
View File
@@ -38,7 +38,7 @@ function resolveDefaultAuthenticationMethod(preferred, current, fallback = 'pass
);
}
module.exports = {
export {
normalizeAuthenticationMethod,
resolveDefaultAuthenticationMethod,
};
+1 -1
View File
@@ -38,4 +38,4 @@ function computeBoardBackground(board) {
return { type: 'none' };
}
module.exports = { computeBoardBackground };
export { computeBoardBackground };
+1 -1
View File
@@ -26,4 +26,4 @@ function remapCopiedBoardDefaults(board, oldId, newId) {
return patch;
}
module.exports = { remapCopiedBoardDefaults };
export { remapCopiedBoardDefaults };
+1 -1
View File
@@ -53,7 +53,7 @@ function cardIdsOnBoard(cardIds, boardId, getCard) {
});
}
module.exports = {
export {
boardScopedFilterSelector,
boardScopedSelectionSelector,
cardIdsOnBoard,
+1 -1
View File
@@ -60,7 +60,7 @@ function computeReorderedSortIndex(orderedIds, draggedId, targetId) {
return computeSortIndexMapping(reordered);
}
module.exports = {
export {
isDragReorderEnabled,
computeReorderedIds,
computeSortIndexMapping,
+2 -2
View File
@@ -17,7 +17,7 @@
// filter is active (or on the server) and the base selector is returned
// unchanged.
const { combineWithFilter } = require('./swimlaneFilter');
import { combineWithFilter } from './swimlaneFilter.js';
// Cards whose due date falls within [start, end].
function cardsDueInBetweenSelector(boardId, start, end, filterSelector) {
@@ -47,7 +47,7 @@ function cardsInIntervalSelector(boardId, start, end, filterSelector) {
);
}
module.exports = {
export {
cardsDueInBetweenSelector,
cardsInIntervalSelector,
};
+1 -1
View File
@@ -31,4 +31,4 @@ function computeCardMoveModifier(card, target = {}) {
return fields;
}
module.exports = { computeCardMoveModifier };
export { computeCardMoveModifier };
+1 -1
View File
@@ -48,4 +48,4 @@ function buildCardSearchOr(term) {
return or;
}
module.exports = { parseNumericSearchTerm, buildCardSearchOr };
export { parseNumericSearchTerm, buildCardSearchOr };
+1 -1
View File
@@ -86,7 +86,7 @@ function computeRepairedDropIndex(orderedCards, prevCard, nextCard, nCards = 1)
return { updates, base, increment };
}
module.exports = {
export {
isDegenerateSortGap,
computeMonotonicSortRepair,
computeRepairedDropIndex,
+1 -1
View File
@@ -28,4 +28,4 @@ function sortWithIdTiebreaker(sort) {
return { _id: 1 };
}
module.exports = { sortWithIdTiebreaker };
export { sortWithIdTiebreaker };
+1 -1
View File
@@ -32,4 +32,4 @@ function buildCardRelativeUrl(card, board) {
return `/b/${boardId}/${slug}/${card._id}`;
}
module.exports = { isThenable, buildCardRelativeUrl };
export { isThenable, buildCardRelativeUrl };
+1 -1
View File
@@ -62,7 +62,7 @@ function windowCountId(listId, swimlaneId) {
return `${listId}::${swimlaneId || ''}`;
}
module.exports = {
export {
DEFAULT_LAZY_THRESHOLD,
resolveCardsLoadingMode,
parseCardsLoadingEnv,
+1 -1
View File
@@ -44,4 +44,4 @@ function buildChecklistItemPayload(title, checklist, sort) {
};
}
module.exports = { parseChecklistItemTitles, buildChecklistItemPayload };
export { parseChecklistItemTitles, buildChecklistItemPayload };
+1 -1
View File
@@ -19,4 +19,4 @@ function commentActivityDisplayText(activity) {
return '';
}
module.exports = { commentActivityDisplayText };
export { commentActivityDisplayText };
+1 -1
View File
@@ -105,7 +105,7 @@ function contrastText(bgHexOrNamed) {
return luminance > 0.179 ? '#000000' : '#ffffff';
}
module.exports = {
export {
NAMED_COLOR_HEX,
isHexColor,
normalizeHex,
+1 -1
View File
@@ -70,4 +70,4 @@ function buildCustomFieldsWD(customFields, definitions) {
return ret;
}
module.exports = { buildCustomFieldsWD, resolveTrueValue };
export { buildCustomFieldsWD, resolveTrueValue };
+1 -1
View File
@@ -47,7 +47,7 @@ function pickDefaultSwimlane(swimlanes) {
return active || swimlanes[0];
}
module.exports = {
export {
defaultSwimlaneId,
defaultSwimlaneFields,
pickDefaultSwimlane,
+1 -1
View File
@@ -35,4 +35,4 @@ function parseNotifyDueHour(envValue, defaultHour = 8) {
return hour;
}
module.exports = { parseNotifyDueDays, parseNotifyDueHour };
export { parseNotifyDueDays, parseNotifyDueHour };
+1 -1
View File
@@ -29,4 +29,4 @@ function isSubmitKey(event, opts = {}) {
return !!(event.ctrlKey || event.metaKey);
}
module.exports = { isSubmitKey };
export { isSubmitKey };
+1 -1
View File
@@ -140,7 +140,7 @@ function buildCsvCardRow(card, result, customFieldMap) {
return currentRow;
}
module.exports = {
export {
lookupProp,
buildCsvCardRow,
};
+1 -1
View File
@@ -22,4 +22,4 @@ function planImportedBoardMember(wekanMember, importerId) {
};
}
module.exports = { planImportedBoardMember };
export { planImportedBoardMember };
+1 -1
View File
@@ -48,4 +48,4 @@ function importedCardDates(card, activityCreatedAt, now) {
};
}
module.exports = { importedCardDates, toValidDateOrNull };
export { importedCardDates, toValidDateOrNull };
+1 -1
View File
@@ -31,7 +31,7 @@ function filterLabelsByTerm(labels, term) {
return labels.filter(label => labelMatchesTerm(label, term));
}
module.exports = {
export {
labelMatchesTerm,
filterLabelsByTerm,
};
+1 -1
View File
@@ -34,4 +34,4 @@ function resolveCoverId(card, getCard) {
return source && source.coverId ? source.coverId : null;
}
module.exports = { coverSourceCard, resolveCoverId };
export { coverSourceCard, resolveCoverId };
+1 -1
View File
@@ -45,4 +45,4 @@ function memberTargetBoardId(card, resolveCard) {
return card.boardId || null;
}
module.exports = { memberTargetCardId, memberTargetBoardId };
export { memberTargetCardId, memberTargetBoardId };
+1 -1
View File
@@ -35,4 +35,4 @@ function isLinkableCardTarget(target, ownRealCardIds = []) {
return true;
}
module.exports = { isLinkPointerCard, isLinkableCardTarget };
export { isLinkPointerCard, isLinkableCardTarget };
+1 -1
View File
@@ -34,4 +34,4 @@ function computeTitleUpdateTargets(card) {
return targets;
}
module.exports = { computeTitleUpdateTargets };
export { computeTitleUpdateTargets };
+1 -1
View File
@@ -81,7 +81,7 @@ function resolveListWidth(options) {
return normalizeListWidth(personalWidth, shared);
}
module.exports = {
export {
DEFAULT_LIST_WIDTH,
MIN_LIST_WIDTH,
isValidListWidth,
+1 -1
View File
@@ -36,7 +36,7 @@ function filterMembersByTerm(users, term) {
return users.filter(user => memberMatchesTerm(user, term));
}
module.exports = {
export {
memberMatchesTerm,
filterMembersByTerm,
};
+1 -1
View File
@@ -12,4 +12,4 @@ function productNameOrDefault(productName) {
return 'WeKan';
}
module.exports = { productNameOrDefault };
export { productNameOrDefault };
+1 -1
View File
@@ -72,7 +72,7 @@ function appendCardTitleFilterToDesc(desc, cardTitle, label) {
return `${base} (${tag}: "${cardTitle}")`;
}
module.exports = {
export {
cardTitleFilterOrWildcard,
cardTitleMatchList,
cardTitleFilterMatches,
+1 -1
View File
@@ -50,4 +50,4 @@ function canSelectBoardInRules(board, ctx, templatesBoardId) {
return boardVisibleToUserContext(board, ctx);
}
module.exports = { canSelectBoardInRules, boardVisibleToUserContext };
export { canSelectBoardInRules, boardVisibleToUserContext };
+1 -1
View File
@@ -25,7 +25,7 @@ function sortCardsByTitle(cards) {
return cards.slice().sort(compareCardTitle);
}
module.exports = {
export {
compareCardTitle,
sortCardsByTitle,
};
+1 -1
View File
@@ -29,4 +29,4 @@ function subtaskCopyChildKinds() {
return ['checklists'];
}
module.exports = { buildCopiedSubtaskFields, subtaskCopyChildKinds };
export { buildCopiedSubtaskFields, subtaskCopyChildKinds };
+1 -1
View File
@@ -49,4 +49,4 @@ function cardsToRestoreWithSwimlane(cards, swimlaneArchivedAt) {
});
}
module.exports = { cardsToArchiveWithSwimlane, cardsToRestoreWithSwimlane };
export { cardsToArchiveWithSwimlane, cardsToRestoreWithSwimlane };
+1 -1
View File
@@ -102,7 +102,7 @@ function filteredListCardsSelector(listId, swimlaneId, filterSelector, otherSwim
);
}
module.exports = {
export {
swimlaneMembershipSelector,
listCardsSelector,
combineWithFilter,
+1 -1
View File
@@ -79,7 +79,7 @@ function isValidCustomColors(color, customColors) {
return customColors.every(c => typeof c === 'string' && HEX_RE.test(c));
}
module.exports = {
export {
THEME_CATEGORIES,
THEME_CATEGORY_ORDER,
CUSTOM_COLOR_COUNT,
+1 -1
View File
@@ -109,7 +109,7 @@ function colorValue(value) {
return isHexColor6(value) ? value : '';
}
module.exports = {
export {
UI_FONTS,
SAFE_FONT_RE,
isSafeFontName,
+1 -1
View File
@@ -122,7 +122,7 @@ async function applyUserDeletionCleanup(plan, collections) {
return result;
}
module.exports = {
export {
isValidUserId,
buildUserDeletionCleanupPlan,
applyUserDeletionCleanup,
+1 -1
View File
@@ -75,7 +75,7 @@ function weekNumberByFirstDay(date, firstDay) {
return Math.floor(diffDays / 7) + 1;
}
module.exports = {
export {
normalizeFirstDay,
startOfWeek,
weekRange,
+1 -1
View File
@@ -49,4 +49,4 @@ function withDeadline(promise, ms, onTimeout) {
});
}
module.exports = { withDeadline };
export { withDeadline };
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the swimlane archive/restore card
// cascade. Run: node tests/archiveSwimlaneCards.test.cjs
@@ -18,7 +19,7 @@ const assert = require('assert');
const {
cardsToArchiveWithSwimlane,
cardsToRestoreWithSwimlane,
} = require('../models/lib/swimlaneArchive');
} = await import('../models/lib/swimlaneArchive.js');
let passed = 0;
function test(name, fn) {
@@ -186,3 +187,5 @@ test('re-archiving after restore archives the restored cards again', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the default authentication-method helpers.
// Run: node tests/authenticationMethod.test.cjs
@@ -15,7 +16,7 @@ const assert = require('assert');
const {
normalizeAuthenticationMethod,
resolveDefaultAuthenticationMethod,
} = require('../models/lib/authenticationMethod');
} = await import('../models/lib/authenticationMethod.js');
let passed = 0;
function test(name, fn) {
@@ -78,3 +79,5 @@ test('NEGATIVE: a real selected value still overrides the stored one', () => {
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the board-background descriptor.
// Run: node tests/boardBackground.test.cjs
@@ -13,7 +14,7 @@
// image. The old code only ever SET a background, never reset it.
const assert = require('assert');
const { computeBoardBackground } = require('../models/lib/boardBackground');
const { computeBoardBackground } = await import('../models/lib/boardBackground.js');
let passed = 0;
function test(name, fn) {
@@ -70,3 +71,5 @@ test('NEGATIVE: null / undefined board is handled without throwing', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the board-copy default-pointer remap.
// Run: node tests/boardCopyDefaults.test.cjs
@@ -11,7 +12,7 @@
// copy (and clear the paired list id so it self-heals on the new board).
const assert = require('assert');
const { remapCopiedBoardDefaults } = require('../models/lib/boardCopyDefaults');
const { remapCopiedBoardDefaults } = await import('../models/lib/boardCopyDefaults.js');
let passed = 0;
function test(name, fn) {
@@ -81,3 +82,5 @@ test('NEGATIVE: the new board must NOT keep the template board id', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for models/lib/boardScopedSelection.js.
// Run: ELECTRON_RUN_AS_NODE=1 <node> tests/boardScopedSelection.test.cjs
@@ -16,7 +17,7 @@ const {
boardScopedFilterSelector,
boardScopedSelectionSelector,
cardIdsOnBoard,
} = require('../models/lib/boardScopedSelection');
} = await import('../models/lib/boardScopedSelection.js');
let passed = 0;
function test(name, fn) {
@@ -210,3 +211,5 @@ test('#2306: selection built from the scoped filter query holds only current-boa
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the All Boards drag-reorder helpers.
// Run: node tests/boardSortReorder.test.cjs
@@ -17,7 +18,7 @@ const {
computeReorderedIds,
computeSortIndexMapping,
computeReorderedSortIndex,
} = require('../models/lib/boardSortReorder');
} = await import('../models/lib/boardSortReorder.js');
let passed = 0;
function test(name, fn) {
@@ -132,3 +133,5 @@ test('#6442: ordered ids read from the wrong DOM class (all "js-board") never re
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the Calendar view's card selector
// builders. Run: node tests/calendarFilter.test.cjs
@@ -15,7 +16,7 @@ const assert = require('assert');
const {
cardsDueInBetweenSelector,
cardsInIntervalSelector,
} = require('../models/lib/calendarFilter');
} = await import('../models/lib/calendarFilter.js');
let passed = 0;
function test(name, fn) {
@@ -134,3 +135,5 @@ test('#5656: without a filter, both users\' cards are shown (pre-fix behaviour p
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the Card.move() modifier builder.
// Run: node tests/cardMoveModifier.test.cjs
@@ -14,7 +15,7 @@
// the caller skips entirely.
const assert = require('assert');
const { computeCardMoveModifier } = require('../models/lib/cardMoveModifier');
const { computeCardMoveModifier } = await import('../models/lib/cardMoveModifier.js');
let passed = 0;
function test(name, fn) {
@@ -115,3 +116,5 @@ test('NEGATIVE: undefined target fields are ignored, not written as undefined',
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the board card-search query builder.
// Run: node tests/cardSearch.test.cjs
@@ -15,7 +16,7 @@ const assert = require('assert');
const {
parseNumericSearchTerm,
buildCardSearchOr,
} = require('../models/lib/cardSearch');
} = await import('../models/lib/cardSearch.js');
let passed = 0;
function test(name, fn) {
@@ -133,3 +134,5 @@ test('non-numeric term against a numeric-only card finds nothing', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Tests for models/lib/cardSortTiebreaker.js (#6511).
//
@@ -13,7 +14,7 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { sortWithIdTiebreaker } = require('../models/lib/cardSortTiebreaker');
const { sortWithIdTiebreaker } = await import('../models/lib/cardSortTiebreaker.js');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
@@ -62,3 +63,5 @@ check('the server card window tiebreaks its limited sort too', () => {
});
console.log(`\ncardSortTiebreaker: ${passed} checks passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the card relative-URL builder.
// Run: node tests/cardUrl.test.cjs
@@ -12,7 +13,7 @@
// The client was unaffected because there Card.board() is synchronous.
const assert = require('assert');
const { isThenable, buildCardRelativeUrl } = require('../models/lib/cardUrl');
const { isThenable, buildCardRelativeUrl } = await import('../models/lib/cardUrl.js');
let passed = 0;
function test(name, fn) {
@@ -105,3 +106,5 @@ test('NEGATIVE: returns undefined for a missing card', () => {
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the CARDS_LOADING / lazy card-loading
// helpers. Run: node tests/cardsLoading.test.cjs
@@ -20,7 +21,7 @@ const {
cardsLoadingLazyThreshold,
effectiveBoardCardsMode,
windowCountId,
} = require('../models/lib/cardsLoading');
} = await import('../models/lib/cardsLoading.js');
let passed = 0;
function test(name, fn) {
@@ -117,3 +118,5 @@ test('different lists never collide', () => {
});
console.log(`\ncardsLoading: ${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the minicard/card-detail "add checklist
// item" text parsing and payload builder.
@@ -15,7 +16,7 @@ const assert = require('assert');
const {
parseChecklistItemTitles,
buildChecklistItemPayload,
} = require('../models/lib/checklistItemTitles');
} = await import('../models/lib/checklistItemTitles.js');
let passed = 0;
function test(name, fn) {
@@ -104,3 +105,5 @@ test('payload does NOT target a different checklist/card', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for comment-activity display text.
// Run: node tests/commentActivity.test.cjs
@@ -10,7 +11,7 @@
// stored text, else the live comment's text, else '' — never "undefined".
const assert = require('assert');
const { commentActivityDisplayText } = require('../models/lib/commentActivity');
const { commentActivityDisplayText } = await import('../models/lib/commentActivity.js');
let passed = 0;
function test(name, fn) {
@@ -71,3 +72,5 @@ test('NEGATIVE: non-string commentText / malformed comment do not leak "undefine
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node regression test (no Meteor) for issue #3843:
// "Keep the link between global attachment and attachment in a comment" —
@@ -31,7 +32,7 @@ const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { buildCardAttachmentMeta } = require('../models/lib/attachmentMeta');
const { buildCardAttachmentMeta } = await import('../models/lib/attachmentMeta.js');
const repoRoot = path.resolve(__dirname, '..');
const read = rel => fs.readFileSync(path.join(repoRoot, rel), 'utf8');
@@ -215,3 +216,5 @@ test('NEGATIVE: a linked card whose real card is gone still links by id and does
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the pure color helpers behind #5514
// (custom color-wheel / RGB hex picker with automatically readable text).
@@ -10,7 +11,7 @@ const {
normalizeHex,
toHex,
contrastText,
} = require('../models/lib/contrastColor');
} = await import('../models/lib/contrastColor.js');
let passed = 0;
function test(name, fn) {
@@ -128,3 +129,5 @@ test('toHex passes hex through (normalized) and rejects unknown names', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the "customFields with definitions"
// matcher used by Cards.customFieldsWD().
@@ -22,7 +23,7 @@ const assert = require('assert');
const {
buildCustomFieldsWD,
resolveTrueValue,
} = require('../models/lib/customFieldsWD');
} = await import('../models/lib/customFieldsWD.js');
let passed = 0;
function test(name, fn) {
@@ -172,3 +173,5 @@ test('resolveTrueValue: non-dropdown and unmatched ids keep the raw value', () =
});
console.log(`\n${passed} passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the default-swimlane self-heal helpers.
// Run: node tests/defaultSwimlane.test.cjs
@@ -17,7 +18,7 @@ const {
defaultSwimlaneId,
defaultSwimlaneFields,
pickDefaultSwimlane,
} = require('../models/lib/defaultSwimlane');
} = await import('../models/lib/defaultSwimlane.js');
let passed = 0;
function test(name, fn) {
@@ -204,3 +205,5 @@ test('#1971 source guard: boards.js getDefaultSwimline uses pickDefaultSwimlane'
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Unit + negative tests for the due-date reminder config parsers (#3192).
// Run: node tests/dueNotificationConfig.test.cjs
@@ -9,7 +10,7 @@ const path = require('path');
const {
parseNotifyDueDays,
parseNotifyDueHour,
} = require('../models/lib/dueNotificationConfig');
} = await import('../models/lib/dueNotificationConfig.js');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
@@ -78,3 +79,5 @@ check('#3192: due-card notifications include card ASSIGNEES as participants', ()
});
console.log(`\ndueNotificationConfig: ${passed} checks passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the editor submit-key rule.
// Run: node tests/editorSubmitKey.test.cjs
@@ -10,7 +11,7 @@
// ONLY on Enter + Ctrl/Cmd; plain Enter and Shift+Enter make a newline.
const assert = require('assert');
const { isSubmitKey } = require('../models/lib/editorSubmitKey');
const { isSubmitKey } = await import('../models/lib/editorSubmitKey.js');
let passed = 0;
function test(name, fn) {
@@ -84,3 +85,5 @@ test('submitOnEnter defaults off: explicit false and omitted both require Ctrl/C
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the CSV export row builder.
// Run: node tests/exporterCsvRow.test.cjs
@@ -14,7 +15,7 @@
// still produce the correct cells for well-formed cards.
const assert = require('assert');
const { buildCsvCardRow, lookupProp } = require('../models/lib/exporterCsvRow');
const { buildCsvCardRow, lookupProp } = await import('../models/lib/exporterCsvRow.js');
let passed = 0;
function test(name, fn) {
@@ -156,3 +157,5 @@ test('lookupProp returns the property for a present id and fallback otherwise',
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+5 -2
View File
@@ -64,8 +64,11 @@ test('import is bounded on the server by a hard deadline', () => {
assert.ok(/'import-timeout'/.test(srv), 'server raises import-timeout');
});
test('the deadline + map-plan helpers are CommonJS (importable + testable)', () => {
assert.ok(/module\.exports = \{ withDeadline \}/.test(read('models/lib/withDeadline.js')));
test('the deadline + map-plan helpers are importable + testable', () => {
// withDeadline is a client-reachable helper, so it is an ES module (export) to keep
// rspack from flagging module.exports as an ES-module assignment; boardMemberMapPlan is
// server-only and stays CommonJS.
assert.ok(/export \{ withDeadline \}/.test(read('models/lib/withDeadline.js')));
assert.ok(/module\.exports = \{ planBoardMemberMapping \}/.test(read('models/lib/boardMemberMapPlan.js')));
});
+4 -1
View File
@@ -1,10 +1,11 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for how an imported board member becomes a member of
// the new board. Run: node tests/importedBoardMemberPlan.test.cjs
const assert = require('assert');
const { planImportedBoardMember } = require('../models/lib/importedBoardMemberPlan');
const { planImportedBoardMember } = await import('../models/lib/importedBoardMemberPlan.js');
let passed = 0;
function test(name, fn) {
@@ -56,3 +57,5 @@ test('null / undefined member is dropped', () => {
});
console.log(`\nimportedBoardMemberPlan: ${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the board-import card date helper.
// Run: node tests/importedCardDates.test.cjs
@@ -15,7 +16,7 @@ const assert = require('assert');
const {
importedCardDates,
toValidDateOrNull,
} = require('../models/lib/importedCardDates');
} = await import('../models/lib/importedCardDates.js');
let passed = 0;
function test(name, fn) {
@@ -116,3 +117,5 @@ test('toValidDateOrNull: valid ISO string gives the matching Date', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Unit + negative tests for the card-title "#" label autocomplete matcher
// (issue #5116): typing "#test" must suggest a label named "Testing".
@@ -8,7 +9,7 @@ const assert = require('assert');
const {
labelMatchesTerm,
filterLabelsByTerm,
} = require('../models/lib/labelAutocomplete');
} = await import('../models/lib/labelAutocomplete.js');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
@@ -92,3 +93,5 @@ check('listBody.js label strategy routes through labelMatchesTerm (no case-sensi
});
console.log(`\nlabelAutocompleteMatch: ${passed} checks passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the linked-card cover resolver.
// Run: node tests/linkedCardCover.test.cjs
@@ -13,7 +14,7 @@ const assert = require('assert');
const {
coverSourceCard,
resolveCoverId,
} = require('../models/lib/linkedCardCover');
} = await import('../models/lib/linkedCardCover.js');
let passed = 0;
function test(name, fn) {
@@ -72,3 +73,5 @@ test('null / typeless cards are handled without throwing', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the linked-card member target helpers.
// Run: node tests/linkedCardMembers.test.cjs
@@ -16,7 +17,7 @@ const assert = require('assert');
const {
memberTargetCardId,
memberTargetBoardId,
} = require('../models/lib/linkedCardMembers');
} = await import('../models/lib/linkedCardMembers.js');
let passed = 0;
function test(name, fn) {
@@ -94,3 +95,5 @@ test('memberTargetBoardId is null-safe', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the "Link to this card" target guard.
// Run: node tests/linkedCardTarget.test.cjs
@@ -14,7 +15,7 @@ const assert = require('assert');
const {
isLinkPointerCard,
isLinkableCardTarget,
} = require('../models/lib/linkedCardTarget');
} = await import('../models/lib/linkedCardTarget.js');
let passed = 0;
function test(name, fn) {
@@ -76,3 +77,5 @@ test('NEGATIVE: cannot link to a template card, or to nothing', () => {
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for linked-card title update targets.
// Run: node tests/linkedCardTitle.test.cjs
@@ -11,7 +12,7 @@
// document, plus the linked target.
const assert = require('assert');
const { computeTitleUpdateTargets } = require('../models/lib/linkedCardTitle');
const { computeTitleUpdateTargets } = await import('../models/lib/linkedCardTitle.js');
let passed = 0;
function test(name, fn) {
@@ -79,3 +80,5 @@ test('NEGATIVE: null / id-less card yields no targets, no throw', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node regression test (no Meteor) for issue #5659:
// "Bug: List Width settings do not affect Public Board".
@@ -31,7 +32,7 @@ const {
isValidListWidth,
normalizeListWidth,
resolveListWidth,
} = require('../models/lib/listWidth');
} = await import('../models/lib/listWidth.js');
const repoRoot = path.resolve(__dirname, '..');
const read = rel => fs.readFileSync(path.join(repoRoot, rel), 'utf8');
@@ -208,3 +209,5 @@ test('the lists schema default matches the shared default', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Unit + negative tests for the "@" member autocomplete matcher (#5116
// follow-up): typing "@ann" must suggest the member "Anna" and username
@@ -11,7 +12,7 @@ const path = require('path');
const {
memberMatchesTerm,
filterMembersByTerm,
} = require('../models/lib/memberAutocomplete');
} = await import('../models/lib/memberAutocomplete.js');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
@@ -91,3 +92,5 @@ function read(rel) {
}
console.log(`\nmemberAutocompleteMatch: ${passed} checks passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Unit tests for the Product-name helper (models/lib/productName.js) and the
// migration-progress modal branding that uses it.
@@ -11,7 +12,7 @@
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { productNameOrDefault } = require('../models/lib/productName');
const { productNameOrDefault } = await import('../models/lib/productName.js');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
@@ -55,3 +56,5 @@ check('migrationProgress.jade shows the Product name in the title', () => {
});
console.log(`\nproductName: ${passed} checks passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the IFTTT-Rules board selector logic.
// Run: node tests/rulesBoardSelector.test.cjs
@@ -16,7 +17,7 @@ const assert = require('assert');
const {
canSelectBoardInRules,
boardVisibleToUserContext,
} = require('../models/lib/rulesBoardSelector');
} = await import('../models/lib/rulesBoardSelector.js');
let passed = 0;
function test(name, fn) {
@@ -132,3 +133,5 @@ test('missing org/team/domain arrays on ctx do not throw', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the board-rule card-title filter.
// Run: ELECTRON_RUN_AS_NODE=1 <node> tests/rulesCardTitleFilter.test.cjs
@@ -21,7 +22,7 @@ const {
cardTitleMatchList,
cardTitleFilterMatches,
appendCardTitleFilterToDesc,
} = require('../models/lib/ruleCardTitleFilter');
} = await import('../models/lib/ruleCardTitleFilter.js');
let passed = 0;
function test(name, fn) {
@@ -141,3 +142,5 @@ test('server matching uses the shared helper for the cardTitle field', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,3 +1,4 @@
(async () => {
/**
* Test: sortCardsByTitle (models/lib/sortCardsByTitle.js).
*
@@ -10,7 +11,7 @@
*/
const assert = require('assert');
const { sortCardsByTitle, compareCardTitle } = require('../models/lib/sortCardsByTitle');
const { sortCardsByTitle, compareCardTitle } = await import('../models/lib/sortCardsByTitle.js');
const titles = cards => cards.map(c => c.title);
@@ -89,3 +90,5 @@ if (failed) {
process.exit(1);
}
console.log('\nAll ' + tests.length + ' tests passed');
})().catch(e => { console.error(e); process.exit(1); });
+5 -2
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the #3826 drag-sort repair helpers.
// Run: ELECTRON_RUN_AS_NODE=1 <node> tests/subtaskCardReorder.test.cjs
@@ -24,8 +25,8 @@ const {
isDegenerateSortGap,
computeMonotonicSortRepair,
computeRepairedDropIndex,
} = require('../models/lib/cardSortRepair');
const { computeCardMoveModifier } = require('../models/lib/cardMoveModifier');
} = await import('../models/lib/cardSortRepair.js');
const { computeCardMoveModifier } = await import('../models/lib/cardMoveModifier.js');
let passed = 0;
function test(name, fn) {
@@ -198,3 +199,5 @@ test('NEGATIVE: the repair must not rewrite cards without an _id (defensive)', (
});
console.log(`\n${passed} passing`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for copying a card's subtasks.
// Run: node tests/subtaskCopy.test.cjs
@@ -12,7 +13,7 @@ const assert = require('assert');
const {
buildCopiedSubtaskFields,
subtaskCopyChildKinds,
} = require('../models/lib/subtaskCopy');
} = await import('../models/lib/subtaskCopy.js');
let passed = 0;
function test(name, fn) {
@@ -80,3 +81,5 @@ test('NEGATIVE: ... nor the SOURCE board id (would strand the subtask)', () => {
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the per-list / per-swimlane card
// selector builder. Run: node tests/swimlaneFilter.test.cjs
@@ -20,7 +21,7 @@ const {
listCardsSelector,
combineWithFilter,
filteredListCardsSelector,
} = require('../models/lib/swimlaneFilter');
} = await import('../models/lib/swimlaneFilter.js');
let passed = 0;
function test(name, fn) {
@@ -246,3 +247,5 @@ test('#6443: label filter still applies alongside the first-swimlane orphaned cl
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the user-deletion cleanup planner.
// Run: ELECTRON_RUN_AS_NODE=1 <node> tests/userDeletionCleanup.test.cjs
@@ -16,7 +17,7 @@ const {
isValidUserId,
buildUserDeletionCleanupPlan,
applyUserDeletionCleanup,
} = require('../models/lib/userDeletionCleanup');
} = await import('../models/lib/userDeletionCleanup.js');
let passed = 0;
function test(name, fn) {
@@ -267,3 +268,5 @@ run.catch(err => {
console.error(err);
process.exit(1);
});
})().catch(e => { console.error(e); process.exit(1); });
+4 -1
View File
@@ -1,4 +1,5 @@
'use strict';
(async () => {
// Plain-Node unit test (no Meteor) for the start-day-of-week math.
// Run: node tests/weekStart.test.cjs
@@ -19,7 +20,7 @@ const {
startOfWeek,
weekRange,
weekNumberByFirstDay,
} = require('../models/lib/weekStart');
} = await import('../models/lib/weekStart.js');
let passed = 0;
function test(name, fn) {
@@ -128,3 +129,5 @@ test('NEGATIVE: start day changes the number for Saturday 2023-01-07 (#4946)', (
});
console.log(`\n${passed} tests passed`);
})().catch(e => { console.error(e); process.exit(1); });