Date templates: one edit form and one badge instead of twenty-two copies.

Two shapes were copied across three files. The edit-a-date form - date, time,
Save, Delete - existed eight times, letter for letter: the card's Received,
Start, Due and End, a vote's end date, a planning poker's end date, a date
custom field, and a ninth `datepicker` template that nothing included and no
popup could open (a popup's template name ends in `Popup`). The date badge -
the coloured date on a card and on a minicard - existed fourteen times.

The JavaScript was already shared: client/lib/datepicker.js holds the state and
the handlers, and each popup differs only in the field it stores. It was only
the markup, so a change to the form meant eight edits and a change to the badge
meant fourteen, with nothing to say so.

Each is one template now - editDateForm and dateBadgeBody. They take what they
draw as ARGUMENTS, because a helper is looked up on the template it is written
in and not on the one including it: that is what lets one piece of markup serve
them all while every popup keeps its own state, its own click and its own name.
cardDate.jade went from 289 lines to 91, and the dead datepicker.js went with
the template it wired up.

The badge's `baseClass` is the trap this had to avoid: three of the fourteen -
the custom-field dates - were never `.card-date` and must not become one, so
the class each caller carried is passed in rather than baked into the shared
markup. tests/dateTemplates.test.cjs pins that, the minicard kind classes, and
that no state moved.

Thanks to xet7 !
This commit is contained in:
Lauri Ojansivu committed 2026-08-14 06:48:47 +03:00
1 parent 1f9848920b
commit 9c288c3d4d
7 files changed
+226 -283

No files matched your search

+29
View File
@@ -1094,6 +1094,35 @@ so Enter and Space do what a click does.
</details>
and has the following developer-facing change:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/COMMITHASH10">The date markup is written once instead of twenty-two times</a>. Thanks to xet7.</summary>
Two shapes were copied across three files. The **edit-a-date form** - date,
time, Save, Delete - existed **eight times**, letter for letter: the card's
Received, Start, Due and End, a vote's end date, a planning poker's end date, a
date custom field, and a ninth `datepicker` template that nothing included and
no popup could open. The **date badge** - the coloured date on a card and on a
minicard - existed **fourteen times**.
The JavaScript was already shared: `client/lib/datepicker.js` holds the state
and the handlers, and each popup differs only in the field it stores. It was
only the markup, so a change to the form meant eight edits and a change to the
badge meant fourteen, with nothing to say so.
Each is one template now. They take what they draw as ARGUMENTS, because a
helper is looked up on the template it is written in and not on the one
including it - which is what lets one piece of markup serve them all while every
popup keeps its own state, its own click and its own name. `cardDate.jade` went
from 289 lines to 91.
The badge's `baseClass` is the trap this had to avoid: three of the fourteen -
the custom-field dates - were never `.card-date` and must not become one, so the
class each caller carried is passed in rather than baked into the shared markup.
</details>
and fixes the following bugs:
**Card details** - the card as it is opened and edited.
+1 -13
View File
@@ -99,19 +99,7 @@ template(name="cardCustomField-date")
| {{showWeek}}
template(name="cardCustomField-datePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="cardCustomField-dropdown")
if canModifyCard
+41 -239
View File
@@ -1,289 +1,91 @@
template(name="dateBadge")
//- One date badge: the coloured date on a card or a minicard. Twelve templates
drew this, letter for letter - Received, Start, Due and End on the card and
again on the minicard, a vote's end, a poker's end and a custom field's date -
and each of them still has its own helpers and its own click, which is the
part that differs. The markup did not.
//-
//- It takes what it draws as ARGUMENTS: a helper is looked up on the template
it is written in, not on the one including it, so passing the values is what
lets one piece of markup serve twelve templates without moving any of their
state.
//-
//- `baseClass` is the class the caller's own markup carried - `card-date`, plus
`due-date` and friends on a minicard. A custom field's date passes NONE: it
was never a `.card-date` and must not become one now.
//- `canModifyCard` decides whether the badge is a link that opens the date
editor; a custom field's date is never one, and passes nothing.
template(name="dateBadgeBody")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{baseClass}} {{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{baseClass}} {{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="dateBadge")
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="cardReceivedDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="cardStartDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="cardDueDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="cardEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="dateCustomField")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="cardCustomFieldDate")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="voteEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="pokerEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="minicardReceivedDate")
if canModifyCard
a.js-edit-date.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard showTitle="{{_ 'card-received'}}" baseClass="card-date received-date" classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="minicardStartDate")
if canModifyCard
a.js-edit-date.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard showTitle="{{_ 'card-start'}}" baseClass="card-date start-date" classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="minicardDueDate")
if canModifyCard
a.js-edit-date.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard showTitle="{{_ 'card-due'}}" baseClass="card-date due-date" classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="minicardEndDate")
if canModifyCard
a.js-edit-date.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(canModifyCard=canModifyCard showTitle="{{_ 'card-end'}}" baseClass="card-date end-date" classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="minicardCustomFieldDate")
a(title="{{_ 'date'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
+dateBadgeBody(showTitle="{{_ 'date'}}" classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="editCardReceivedDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="editCardStartDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="editCardDueDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="editCardEndDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="editVoteEndDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
template(name="editPokerEndDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+editDateForm(showDate=showDate showTime=showTime error=error)
+29 -14
View File
@@ -1,14 +1,29 @@
template(name="datepicker")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
//- The date-and-time form every "edit a date" popup is made of: the card's
//- Received, Start, Due and End dates, a vote's and a planning poker's end
//- date, and a date custom field. Eight popups drew this same form, letter for
//- letter, in three different files.
//-
//- It takes what it shows as ARGUMENTS rather than reading helpers: a helper
//- inside a template is looked up on that template, not on the one including
//- it, and all eight popups keep their own state and handlers
//- (client/lib/datepicker.js, which they already shared - it was only the
//- markup that was copied). So this template has no code of its own at all;
//- it renders three values and the buttons.
//-
//- The buttons are handled where the state is: `js-submit-date` and
//- `js-delete-date` bubble to the popup that includes this, whose events come
//- from datePickerEvents().
template(name="editDateForm")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
-16
View File
@@ -1,16 +0,0 @@
import {
setupDatePicker,
datePickerRendered,
datePickerHelpers,
datePickerEvents,
} from '/client/lib/datepicker';
Template.datepicker.onCreated(function () {
setupDatePicker(this);
});
Template.datepicker.onRendered(function () {
datePickerRendered(this);
});
Template.datepicker.helpers(datePickerHelpers());
+7 -1
View File
@@ -2,7 +2,13 @@ import '/client/components/forms/datepicker.jade';
import '/client/components/forms/inlinedform.jade';
import '/client/lib/inlinedform';
import '/client/components/forms/datepicker.js';
// No datepicker.js: the template in datepicker.jade is `editDateForm`, which
// renders what it is passed and has no state of its own. The state and the
// handlers belong to the eight popups that include it, and come from
// client/lib/datepicker.js. The template this file used to wire up - a ninth
// copy of the same form, called `datepicker` - was included by nothing and
// could not be opened as a popup either (a popup's template name ends in
// `Popup`), so it went with the duplication.
import '/client/components/forms/datepicker.css';
import '/client/components/forms/forms.css';
+119
View File
@@ -0,0 +1,119 @@
'use strict';
// The date markup is written once.
// Run: node tests/dateTemplates.test.cjs
//
// Two shapes were copied across three files: the "edit a date" form (eight
// identical copies - the card's Received, Start, Due and End, a vote's end, a
// poker's end, a custom field's date, and a ninth `datepicker` template nothing
// included) and the date BADGE (fourteen copies - the same four dates on the
// card and again on the minicard, a generic one, the two end dates, and three
// custom-field ones).
//
// The JavaScript was already shared: client/lib/datepicker.js has the state and
// the handlers, and each popup differs only in what it stores. Only the markup
// was duplicated, so a change to the form meant eight edits and a change to the
// badge meant fourteen, with nothing to say so.
//
// Each shape is one template now, and it takes what it draws as ARGUMENTS -
// because a helper is looked up on the template it is written in, not on the
// one including it. That is what lets the markup be shared while every popup
// keeps its own state, its own click and its own name.
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const read = rel => fs.readFileSync(path.join(ROOT, rel), 'utf8');
const cardDate = read('client/components/cards/cardDate.jade');
const customFields = read('client/components/cards/cardCustomFields.jade');
const datepicker = read('client/components/forms/datepicker.jade');
let passed = 0;
function test(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
console.log('dateTemplates:');
test('the edit form is written once', () => {
assert.ok(/template\(name="editDateForm"\)/.test(datepicker), 'one template');
const copies = (cardDate + customFields + datepicker)
.match(/input\.js-date-field#date/g) || [];
assert.strictEqual(copies.length, 1, 'and the date field exists in exactly one place');
// All eight popups include it, and pass the three things it draws.
for (const popup of ['editCardReceivedDatePopup', 'editCardStartDatePopup',
'editCardDueDatePopup', 'editCardEndDatePopup', 'editVoteEndDatePopup',
'editPokerEndDatePopup']) {
const at = cardDate.indexOf(`template(name="${popup}")`);
assert.ok(at !== -1, `${popup} still exists`);
assert.ok(/\+editDateForm\(showDate=showDate showTime=showTime error=error\)/
.test(cardDate.slice(at, at + 200)), `${popup} includes it`);
}
assert.ok(/\+editDateForm\(showDate=showDate showTime=showTime error=error\)/.test(customFields),
'and so does the custom field date popup');
});
test('the badge is written once', () => {
assert.ok(/template\(name="dateBadgeBody"\)/.test(cardDate), 'one template');
const links = cardDate.match(/a\.js-edit-date/g) || [];
assert.strictEqual(links.length, 1, 'and the badge link exists in exactly one place');
const includes = cardDate.match(/\+dateBadgeBody\(/g) || [];
assert.strictEqual(includes.length, 14, 'fourteen templates draw it');
});
test('a custom field date is still not a .card-date (negative)', () => {
// The trap in sharing one badge: three of the fourteen never carried
// `card-date`, and folding them into a template that adds it would restyle
// them. `baseClass` is what the caller's own markup carried.
const body = cardDate.slice(cardDate.indexOf('template(name="dateBadgeBody")'),
cardDate.indexOf('template(name="dateBadge")'));
assert.ok(/class="\{\{baseClass\}\} \{\{classes\}\}"/.test(body),
'the base class comes from the caller');
assert.ok(!/a\.js-edit-date\.card-date|a\.card-date/.test(body),
'and is not baked into the shared markup');
for (const plain of ['dateCustomField', 'cardCustomFieldDate', 'minicardCustomFieldDate']) {
const at = cardDate.indexOf(`template(name="${plain}")`);
const include = cardDate.slice(at, cardDate.indexOf('\n', cardDate.indexOf('+dateBadgeBody', at)));
assert.ok(!/baseClass=/.test(include), `${plain} passes no base class`);
assert.ok(!/canModifyCard=/.test(include), `${plain} is not a link either, as before`);
}
});
test('a minicard badge keeps its own kind class (negative)', () => {
// `.due-date` and friends are what colour it; they moved into `baseClass`,
// not away.
for (const [tpl, kind] of [['minicardReceivedDate', 'received-date'],
['minicardStartDate', 'start-date'], ['minicardDueDate', 'due-date'],
['minicardEndDate', 'end-date']]) {
const at = cardDate.indexOf(`template(name="${tpl}")`);
const include = cardDate.slice(at, cardDate.indexOf('\n', cardDate.indexOf('+dateBadgeBody', at)));
assert.ok(include.includes(`baseClass="card-date ${kind}"`), `${tpl} keeps ${kind}`);
}
});
test('the ninth copy of the form is gone, with the file that wired it', () => {
// `datepicker` was a template nothing included, and a popup cannot open it
// either - a popup's template name ends in `Popup`.
assert.ok(!/template\(name="datepicker"\)/.test(datepicker), 'the template is gone');
assert.ok(!fs.existsSync(path.join(ROOT, 'client/components/forms/datepicker.js')),
'and so is its module');
const forms = read('client/features/forms.js');
assert.ok(!/datepicker\.js/.test(forms.replace(/\/\/.*/g, '')), 'nothing imports it');
assert.ok(/client\/lib\/datepicker\.js/.test(forms), 'and the note says where the state lives');
});
test('the state and the handlers did not move', () => {
// The point: only markup was shared. Each popup still sets itself up and
// stores its own field.
const js = read('client/components/cards/cardDate.js');
for (const [popup, setter] of [['editCardReceivedDatePopup', 'setReceived'],
['editCardStartDatePopup', 'setStart'], ['editCardDueDatePopup', 'setDue'],
['editCardEndDatePopup', 'setEnd']]) {
assert.ok(js.includes(`Template.${popup}.onCreated`), `${popup} still has its own state`);
assert.ok(js.includes(setter), `${popup} still stores its own field`);
}
assert.ok(/datePickerEvents\(/.test(js), 'from the handlers they already shared');
});
console.log(`\ndateTemplates: ${passed} tests passed`);