mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 01:20:07 -05:00
Mobile mode: Add List moves into the list header, no row of its own.
On a phone the "+ Add List" composer stood as a row above the lists, spending a whole
row on one button. The + now sits in each list header, BETWEEN the caret and the drag
handle, so the caret moves left and the + takes the middle of that group.
This is what desktop already did. #6465 moved Add List into the list header there and
left the mini-screen path alone, so the two had drifted apart. The mobile button now
uses the SAME class (js-add-list-here), therefore the same click handler and the same
behaviour - open the inline composer after this list - and the same Font Awesome icon,
fa-plus-square-o. One button in two layouts rather than two lookalikes that can drift
again.
The mobile list-header grid grows from four columns to five: title, caret, +, handle.
RTL mirrors on its own. Nothing here is placed with a physical left/right: the header
is a CSS grid, and a grid lays out along the INLINE axis, so under dir="rtl" column 1
is on the right and the whole row reverses - handle, +, caret, title. A test asserts
no physical side creeps into any of the three placements, since one `right:` would
strand a control on the wrong edge.
An empty swimlane and an empty board keep a + of their own, as on desktop: there is no
list header to host the button when there are no lists, and without it the first list
could never be created.
The addListForm template is removed. It rendered that standing row, mobile mode was
its last caller, and it is dead once the button moves. Its three handler blocks go
with it - a Template.X.events() left on a removed template throws at MODULE LOAD and
takes out every module after it in the client bundle, which is how the password field
disappeared from /sign-in in 696c49038. tests/templateHandlersExist.test.cjs covers
that, and passes.
Two existing suites referred to addListForm in comments describing the old shape;
both are updated to describe the current one.
Tests: 1332 passing.
Thanks to xet7 !
This commit is contained in:
1 parent
a59c6e7c31
commit
61979c0f6c
8 files changed
+193
-103
No files matched your search
@@ -220,7 +220,9 @@
|
||||
padding: 1rem !important;
|
||||
font-size: 18px !important;
|
||||
display: grid !important;
|
||||
grid-template-columns: 30px 1fr auto auto !important;
|
||||
/* title | caret | add-list + | drag handle. The grid follows the INLINE axis, so
|
||||
under dir="rtl" column 1 is on the right and the whole row mirrors by itself. */
|
||||
grid-template-columns: 30px 1fr auto auto auto !important;
|
||||
gap: 10px !important;
|
||||
align-items: center !important;
|
||||
position: relative !important;
|
||||
@@ -275,13 +277,24 @@
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* The add-list + between the caret (column 3) and the drag handle (column 5). */
|
||||
.mobile-mode .list-header .list-header-add-list {
|
||||
position: static !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 24px !important;
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.mobile-mode .list-header .list-header-handle {
|
||||
position: static !important;
|
||||
inset-inline-end: auto !important;
|
||||
top: auto !important;
|
||||
transform: none !important;
|
||||
grid-row: 1/3 !important;
|
||||
grid-column: 4 !important;
|
||||
grid-column: 5 !important;
|
||||
padding: 14px !important;
|
||||
font-size: 28px !important;
|
||||
text-align: center !important;
|
||||
|
||||
@@ -75,6 +75,16 @@ template(name="listHeader")
|
||||
else
|
||||
a.list-header-menu-icon.js-select-list
|
||||
i.fa.fa-caret-right
|
||||
//- The add-list + sits BETWEEN the caret and the drag handle, so mobile
|
||||
//- mode needs no "Add List" row of its own. Same class, same handler and
|
||||
//- the same Font Awesome icon as the desktop list header uses, so the two
|
||||
//- are one button in two layouts rather than two lookalikes.
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
unless currentUser.isWorker
|
||||
a.js-add-list-here.list-header-add-list(title="{{_ 'add-list'}}")
|
||||
i.fa.fa-plus-square-o
|
||||
unless currentUser.isWorker
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.list-header-handle.handle.js-list-handle
|
||||
|
||||
@@ -9,11 +9,25 @@ template(name="swimlane")
|
||||
if currentListIsInThisSwimlane _id
|
||||
+list(currentList)
|
||||
unless currentList
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
//- #6465 in mobile mode too: no standing "Add List" row of its own. The
|
||||
//- + lives in each list header (between the caret and the drag handle),
|
||||
//- and opens the inline composer after that list - the same behaviour as
|
||||
//- desktop. An empty swimlane has no list header to host a +, so it keeps
|
||||
//- one of its own; without it the first list could never be created.
|
||||
if swimlaneHasNoLists
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
if isEmptyAddListOpen _id
|
||||
+addListInline
|
||||
else
|
||||
.list.list-composer.js-list-composer.js-empty-add-list-button.mini-list
|
||||
.list-header-add
|
||||
a.open-list-composer.js-open-empty-add-list(title="{{_ 'add-list'}}" data-swimlane="{{_id}}")
|
||||
i.fa.fa-plus
|
||||
each lists
|
||||
+miniList(this)
|
||||
if isAddListAfter _id
|
||||
+addListInline
|
||||
else
|
||||
//- #6465: no standing Add List column. Empty swimlane gets the inline
|
||||
//- composer at the start; otherwise it opens after a list via its header
|
||||
@@ -45,13 +59,24 @@ template(name="listsGroup")
|
||||
if currentList
|
||||
+list(currentList)
|
||||
else
|
||||
//- Issue #6142: allow adding a list in Lists (board-view-lists) mode too.
|
||||
if defaultSwimlane
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm(defaultSwimlane)
|
||||
//- Issue #6142: adding a list is possible in Lists (board-view-lists) mode
|
||||
//- too - now from each list header's + rather than a standing row, as in
|
||||
//- the swimlane view above and on desktop.
|
||||
if boardHasNoLists
|
||||
if defaultSwimlane
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
if isEmptyAddListOpen defaultSwimlane._id
|
||||
+addListInline
|
||||
else
|
||||
.list.list-composer.js-list-composer.js-empty-add-list-button.mini-list
|
||||
.list-header-add
|
||||
a.open-list-composer.js-open-empty-add-list(title="{{_ 'add-list'}}" data-swimlane="{{defaultSwimlane._id}}")
|
||||
i.fa.fa-plus
|
||||
each lists
|
||||
+miniList(this)
|
||||
if isAddListAfter _id
|
||||
+addListInline
|
||||
else
|
||||
//- #6465: Lists view — empty board gets the inline composer at the start;
|
||||
//- otherwise it opens after a list via that list's header add-list button.
|
||||
@@ -76,34 +101,6 @@ template(name="listsGroup")
|
||||
if currentCardIsInThisList _id null
|
||||
+cardDetails(currentCard)
|
||||
|
||||
template(name="addListForm")
|
||||
unless currentUser.isWorker
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isReadOnly
|
||||
unless currentUser.isReadAssignedOnly
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
.list-header-add
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
autocomplete="off" autofocus)
|
||||
if swimlaneLists.length
|
||||
| {{_ 'add-after-list'}}
|
||||
select.list-position-input.full-line
|
||||
each swimlaneLists
|
||||
option(value="{{_id}}") {{title}}
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'save'}}
|
||||
.js-close-inlined-form
|
||||
i.fa.fa-times-thin
|
||||
unless currentBoard.isTemplatesBoard
|
||||
unless currentBoard.isTemplateBoard
|
||||
span.quiet
|
||||
| {{_ 'or'}}
|
||||
a.js-list-template {{_ 'template'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form(title="{{_ 'add-list'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
//- #6465: always-open inline Add List composer, rendered AFTER a list (right in
|
||||
//- LTR / left in RTL, via the flex-row lists lane) when that list's header
|
||||
//- add-list button is clicked, or at the start of an empty swimlane/board.
|
||||
|
||||
@@ -21,7 +21,7 @@ function saveSorting(ui) {
|
||||
// of the previous and the following list -- if any.
|
||||
// Use prevAll/nextAll (not prev/next) so non-list siblings between lists do
|
||||
// not break neighbour detection: the lists container also renders the
|
||||
// +addListForm composer and, when a card is open, a +cardDetails element.
|
||||
// +addListInline composer and, when a card is open, a +cardDetails element.
|
||||
// jQuery's .prev('.js-list') only matches an *immediately* adjacent sibling,
|
||||
// so an interspersed composer/cardDetails made calculateIndex mis-detect the
|
||||
// first/last position and compute a wrong sort, so the reordered list landed
|
||||
@@ -856,63 +856,6 @@ Template.swimlane.events({
|
||||
});
|
||||
|
||||
|
||||
Template.addListForm.onCreated(function () {
|
||||
this.currentBoard = Utils.getCurrentBoard();
|
||||
this.isListTemplatesSwimlane =
|
||||
this.currentBoard.isTemplatesBoard() &&
|
||||
Template.currentData().isListTemplatesSwimlane();
|
||||
this.currentSwimlane = Template.currentData();
|
||||
});
|
||||
|
||||
Template.addListForm.helpers({
|
||||
swimlaneLists() {
|
||||
const swimlane = Template.instance().currentSwimlane;
|
||||
if (!swimlane?._id) return [];
|
||||
return ReactiveCache.getLists(
|
||||
{ swimlaneId: swimlane._id, archived: false },
|
||||
{ sort: { sort: 1 } },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Template.addListForm.events({
|
||||
async submit(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
|
||||
const titleInput = tpl.find('.list-name-input');
|
||||
const title = titleInput?.value.trim();
|
||||
|
||||
if (!title) return;
|
||||
|
||||
const positionInput = tpl.find('.list-position-input');
|
||||
const afterListId =
|
||||
positionInput && positionInput.value ? positionInput.value.trim() : null;
|
||||
const nextListId =
|
||||
positionInput &&
|
||||
positionInput.selectedIndex >= 0 &&
|
||||
positionInput.options[positionInput.selectedIndex + 1]
|
||||
? positionInput.options[positionInput.selectedIndex + 1].value
|
||||
: null;
|
||||
|
||||
try {
|
||||
await Meteor.callAsync('createListAfter', {
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
swimlaneId: tpl.currentSwimlane._id,
|
||||
afterListId,
|
||||
nextListId,
|
||||
type: tpl.isListTemplatesSwimlane ? 'template-list' : 'list',
|
||||
});
|
||||
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
} catch (error) {
|
||||
console.error('Failed to create list after selected list:', error);
|
||||
}
|
||||
},
|
||||
'click .js-list-template': Popup.open('searchElement'),
|
||||
});
|
||||
|
||||
// #6465: the inline add-list composer that opens after a specific list (or at the
|
||||
// start of an empty swimlane). Its data context is the swimlane it creates the
|
||||
// list in; the target list (for positioning) is Session 'wekan-add-list-after'.
|
||||
@@ -995,7 +938,7 @@ Template.listsGroup.helpers({
|
||||
// Issue #6142: the add-list composer needs a swimlane as its data context
|
||||
// (it creates the list in that swimlane). In Lists mode the listsGroup data
|
||||
// context is the board, not a swimlane, so resolve the board's default
|
||||
// swimlane here and hand it to +addListForm.
|
||||
// swimlane here, for the empty-board + button and the inline composer.
|
||||
defaultSwimlane() {
|
||||
const board = Template.currentData();
|
||||
const swimlaneId = defaultSwimlaneIdForBoard(board);
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "meteor test --once --driver-package meteortesting:mocha",
|
||||
"test:unit:node": "node tests/filenameSanitizer.test.cjs && node tests/cardBoardConsistency.test.cjs && node tests/cardUrl.test.cjs && node tests/defaultSwimlane.test.cjs && node tests/cardMoveModifier.test.cjs && node tests/authenticationMethod.test.cjs && node tests/linkedCardTarget.test.cjs && node tests/staleWhileRevalidate.test.cjs && node tests/restArrayParam.test.cjs && node tests/minicardLabelHeight.test.cjs && node tests/dateUtils.normalizeDigits.test.cjs && node tests/reconcileBoardTeamMembers.test.cjs && node tests/rulesBoardSelector.test.cjs && node tests/ruleActionResolve.test.cjs && node tests/boardBackground.test.cjs && node tests/weekStart.test.cjs && node tests/ldapUsernameSlug.test.cjs && node tests/ldapGroupAllowlist.test.cjs && node tests/ldapConnectionRelease.test.cjs && node tests/htmljsArrayContent.test.cjs && node tests/cardDescriptionDraft.test.cjs && node tests/commentDraft.test.cjs && node tests/attachmentDeleteGuard.test.cjs && node tests/ruleMoveAction.test.cjs && node tests/snapMigrationRecovery.test.cjs && node tests/migrationAttachmentExtraction.test.cjs && node tests/attachmentRepair.test.cjs && node tests/attachmentStoragePath.test.cjs && node tests/schemaUpgradeSteps.test.cjs && node tests/cpuExec.test.cjs && node tests/cpuExecWiring.test.cjs && node tests/ferretdbHasData.test.cjs && node tests/ferretdbPolling.test.cjs && node tests/ferretdbWaitTimeout.test.cjs && node tests/ferretdbOplog.test.cjs && node tests/ferretdbOplogReset.test.cjs && node tests/ferretdbTextDataBackup.test.cjs && node tests/recoveryPlan.test.cjs && node tests/recoveryReportQuery.test.cjs && node tests/recoveryEventsJsonl.test.cjs && node tests/recoveryReportWiring.test.cjs && node tests/recoveryMaintenance.test.cjs && node tests/importMemberMapping.test.cjs && node tests/boardMemberMapPlan.test.cjs && node tests/withDeadline.test.cjs && node tests/importVirtualMembers.test.cjs && node tests/sandstormMigrationBridge.test.cjs && node tests/fileNameDisplay.test.cjs && node tests/uploadFileName.test.cjs && node tests/diskSpace.test.cjs && node tests/fileTypeCorrection.test.cjs && node tests/fileHardeningGuards.test.cjs && node tests/cpuHighTracker.test.cjs && node tests/cpuMonitorWiring.test.cjs && node tests/buttonThemeColors.test.cjs && node tests/uiDensity.test.cjs && node tests/editorSubmitKey.test.cjs && node tests/isoWeek.test.cjs && node tests/boardCopyDefaults.test.cjs && node tests/linkedCardTitle.test.cjs && node tests/commentActivity.test.cjs && node tests/swimlaneSort.test.cjs && node tests/subtaskCopy.test.cjs && node tests/subtasksDefaultBoard.test.cjs && node tests/scrollbarCss.test.cjs && node tests/minicardBidiIsolation.test.cjs && node tests/notificationCleanup.test.cjs && node tests/cardSearch.test.cjs && node tests/linkedCardMembers.test.cjs && node tests/verificationEmail.test.cjs && node tests/boardSortReorder.test.cjs && node tests/checklistItemTitles.test.cjs && node tests/swimlaneFilter.test.cjs && node tests/popupOffset.test.cjs && node tests/popupAfterConfirm.test.cjs && node tests/defaultBoard.test.cjs && node tests/upcomingUiGuards.test.cjs && node tests/linkedCardCover.test.cjs && node tests/inviteEmailLanguage.test.cjs && node tests/domainTablePage.test.cjs && node tests/mongoSelectorSafety.test.cjs && node tests/filterDateNull.test.cjs && node tests/listMoveSwimlane.test.cjs && node tests/cardDragSwimlaneScrollRefresh.test.cjs && node tests/undoRedoSelection.test.cjs && node tests/softDelete.test.cjs && node tests/globalThemeColor.test.cjs && node tests/themeCategories.test.cjs && node tests/themeColorPicker.test.cjs && node tests/uiFonts.test.cjs && node tests/sandstormAutoOpen.test.cjs && node tests/createListParams.test.cjs && node tests/addBoardTileHeight.test.cjs && node tests/multiSelectionButton.test.cjs && node tests/headerHamburger.test.cjs && node tests/changeLanguageColumns.test.cjs && node tests/cardsLoading.test.cjs && node tests/base64Chunk.test.cjs && node tests/backupPaths.test.cjs && node tests/backupCron.test.cjs && node tests/avatarUrlSafety.test.cjs && node tests/changeAvatarPopup.test.cjs && node tests/devOverlayRuntimeErrors.test.cjs && node tests/boardHeaderOneLine.test.cjs && node tests/headerDragscroll.test.cjs && node tests/jadeCompiles.test.cjs && node tests/mobileModeFullWidth.test.cjs && node tests/minicardDragHandle.test.cjs && node tests/mobileModeConsistency.test.cjs && node tests/securityReportScript.test.cjs && node tests/tablePage.test.cjs && node tests/leftMenu.test.cjs && node tests/templateHandlersExist.test.cjs && node tests/noViewportSpacing.test.cjs && node tests/boardsVisibilitySettings.test.cjs && node tests/snapMigrationSwitchOrder.test.cjs && node tests/importedUserReconciliationPlan.test.cjs && node tests/ldapPhoto.test.cjs && node tests/importedBoardMemberPlan.test.cjs && node tests/migrateNonFinite.test.cjs && node tests/addListComposerOptions.test.cjs && node tests/sidebarWidth.test.cjs && node tests/sidebarResize.testLine truncated
|
||||
"test:unit:node": "node tests/filenameSanitizer.test.cjs && node tests/cardBoardConsistency.test.cjs && node tests/cardUrl.test.cjs && node tests/defaultSwimlane.test.cjs && node tests/cardMoveModifier.test.cjs && node tests/authenticationMethod.test.cjs && node tests/linkedCardTarget.test.cjs && node tests/staleWhileRevalidate.test.cjs && node tests/restArrayParam.test.cjs && node tests/minicardLabelHeight.test.cjs && node tests/dateUtils.normalizeDigits.test.cjs && node tests/reconcileBoardTeamMembers.test.cjs && node tests/rulesBoardSelector.test.cjs && node tests/ruleActionResolve.test.cjs && node tests/boardBackground.test.cjs && node tests/weekStart.test.cjs && node tests/ldapUsernameSlug.test.cjs && node tests/ldapGroupAllowlist.test.cjs && node tests/ldapConnectionRelease.test.cjs && node tests/htmljsArrayContent.test.cjs && node tests/cardDescriptionDraft.test.cjs && node tests/commentDraft.test.cjs && node tests/attachmentDeleteGuard.test.cjs && node tests/ruleMoveAction.test.cjs && node tests/snapMigrationRecovery.test.cjs && node tests/migrationAttachmentExtraction.test.cjs && node tests/attachmentRepair.test.cjs && node tests/attachmentStoragePath.test.cjs && node tests/schemaUpgradeSteps.test.cjs && node tests/cpuExec.test.cjs && node tests/cpuExecWiring.test.cjs && node tests/ferretdbHasData.test.cjs && node tests/ferretdbPolling.test.cjs && node tests/ferretdbWaitTimeout.test.cjs && node tests/ferretdbOplog.test.cjs && node tests/ferretdbOplogReset.test.cjs && node tests/ferretdbTextDataBackup.test.cjs && node tests/recoveryPlan.test.cjs && node tests/recoveryReportQuery.test.cjs && node tests/recoveryEventsJsonl.test.cjs && node tests/recoveryReportWiring.test.cjs && node tests/recoveryMaintenance.test.cjs && node tests/importMemberMapping.test.cjs && node tests/boardMemberMapPlan.test.cjs && node tests/withDeadline.test.cjs && node tests/importVirtualMembers.test.cjs && node tests/sandstormMigrationBridge.test.cjs && node tests/fileNameDisplay.test.cjs && node tests/uploadFileName.test.cjs && node tests/diskSpace.test.cjs && node tests/fileTypeCorrection.test.cjs && node tests/fileHardeningGuards.test.cjs && node tests/cpuHighTracker.test.cjs && node tests/cpuMonitorWiring.test.cjs && node tests/buttonThemeColors.test.cjs && node tests/uiDensity.test.cjs && node tests/editorSubmitKey.test.cjs && node tests/isoWeek.test.cjs && node tests/boardCopyDefaults.test.cjs && node tests/linkedCardTitle.test.cjs && node tests/commentActivity.test.cjs && node tests/swimlaneSort.test.cjs && node tests/subtaskCopy.test.cjs && node tests/subtasksDefaultBoard.test.cjs && node tests/scrollbarCss.test.cjs && node tests/minicardBidiIsolation.test.cjs && node tests/notificationCleanup.test.cjs && node tests/cardSearch.test.cjs && node tests/linkedCardMembers.test.cjs && node tests/verificationEmail.test.cjs && node tests/boardSortReorder.test.cjs && node tests/checklistItemTitles.test.cjs && node tests/swimlaneFilter.test.cjs && node tests/popupOffset.test.cjs && node tests/popupAfterConfirm.test.cjs && node tests/defaultBoard.test.cjs && node tests/upcomingUiGuards.test.cjs && node tests/linkedCardCover.test.cjs && node tests/inviteEmailLanguage.test.cjs && node tests/domainTablePage.test.cjs && node tests/mongoSelectorSafety.test.cjs && node tests/filterDateNull.test.cjs && node tests/listMoveSwimlane.test.cjs && node tests/cardDragSwimlaneScrollRefresh.test.cjs && node tests/undoRedoSelection.test.cjs && node tests/softDelete.test.cjs && node tests/globalThemeColor.test.cjs && node tests/themeCategories.test.cjs && node tests/themeColorPicker.test.cjs && node tests/uiFonts.test.cjs && node tests/sandstormAutoOpen.test.cjs && node tests/createListParams.test.cjs && node tests/addBoardTileHeight.test.cjs && node tests/multiSelectionButton.test.cjs && node tests/headerHamburger.test.cjs && node tests/changeLanguageColumns.test.cjs && node tests/cardsLoading.test.cjs && node tests/base64Chunk.test.cjs && node tests/backupPaths.test.cjs && node tests/backupCron.test.cjs && node tests/avatarUrlSafety.test.cjs && node tests/changeAvatarPopup.test.cjs && node tests/devOverlayRuntimeErrors.test.cjs && node tests/boardHeaderOneLine.test.cjs && node tests/headerDragscroll.test.cjs && node tests/jadeCompiles.test.cjs && node tests/mobileModeFullWidth.test.cjs && node tests/minicardDragHandle.test.cjs && node tests/mobileModeConsistency.test.cjs && node tests/mobileAddList.test.cjs && node tests/securityReportScript.test.cjs && node tests/tablePage.test.cjs && node tests/leftMenu.test.cjs && node tests/templateHandlersExist.test.cjs && node tests/noViewportSpacing.test.cjs && node tests/boardsVisibilitySettings.test.cjs && node tests/snapMigrationSwitchOrder.test.cjs && node tests/importedUserReconciliationPlan.test.cjs && node tests/ldapPhoto.test.cjs && node tests/importedBoardMemberPlan.test.cjs && node tests/migrateNonFinite.test.cjs && node tests/addListComposerOptions.test.cjs && node tests/sidebarWidth.testLine truncated
|
||||
"test:unit:all": "npm run test:unit:node && node tests/cards.stickers.test.js && node tests/stickers.test.js && node tests/stickers.packs.test.js && node tests/linkCardPopup.test.js && node tests/trelloCreator.import.test.js && node tests/trelloImport.maplink.test.js && node tests/trelloImport.zip.test.js && node tests/wekanCreator.import.test.js && node tests/wekanCreator.inconsistent.test.js && node tests/unit/oauth2-admin-groups.test.js",
|
||||
"test:watch": "meteor test --driver-package meteortesting:mocha",
|
||||
"test:e2e": "node tests/e2e/list-regressions.js",
|
||||
|
||||
@@ -26,8 +26,9 @@ const jade = fs.readFileSync(
|
||||
const js = fs.readFileSync(
|
||||
path.join(root, 'client/components/swimlanes/swimlanes.js'), 'utf8');
|
||||
|
||||
// Isolate the addListInline template block from the .jade so we assert on IT,
|
||||
// not on the still-present addListForm template.
|
||||
// Isolate the addListInline template block from the .jade so we assert on IT and not
|
||||
// on a neighbouring template. (The pre-#6465 addListForm template is gone: mobile mode
|
||||
// was its last caller, and it now uses the per-list-header + like desktop.)
|
||||
function templateBlock(src, name) {
|
||||
const start = src.indexOf(`template(name="${name}")`);
|
||||
assert.ok(start >= 0, `template ${name} must exist`);
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
|
||||
// Mobile mode has no "Add List" row of its own. The + lives in each list header,
|
||||
// BETWEEN the caret and the drag handle.
|
||||
//
|
||||
// Requested: the standing "+ Add List" row wasted a whole row on a phone, and it was
|
||||
// the last caller of the pre-#6465 addListForm template - desktop had already moved
|
||||
// Add List into the list header. Now both do the same thing, with the same class, the
|
||||
// same handler and the same Font Awesome icon, so they are one button in two layouts
|
||||
// rather than two lookalikes that can drift.
|
||||
//
|
||||
// RTL: nothing is placed with a physical left/right. The header is a CSS grid, and a
|
||||
// grid follows the INLINE axis - under dir="rtl" column 1 is on the right, so the
|
||||
// whole row mirrors on its own: handle, +, caret, then the title.
|
||||
//
|
||||
// Run: node tests/mobileAddList.test.cjs
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passed = 0;
|
||||
function test(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
const read = rel => fs.readFileSync(path.join(root, rel), 'utf8');
|
||||
|
||||
const listHeader = read('client/components/lists/listHeader.jade');
|
||||
const swim = read('client/components/swimlanes/swimlanes.jade');
|
||||
const swimJs = read('client/components/swimlanes/swimlanes.js');
|
||||
const css = read('client/components/boards/boardHeader.css');
|
||||
|
||||
// The mini-screen, list-overview branch of the list header - the rows a phone shows
|
||||
// when no single list is selected.
|
||||
const miniBranch = (() => {
|
||||
const at = listHeader.indexOf('a.list-header-menu-icon.js-select-list');
|
||||
assert.ok(at > -1, 'the mini-screen list row must exist');
|
||||
const end = listHeader.indexOf('else if currentUser.isBoardMember', at);
|
||||
return listHeader.slice(at, end === -1 ? undefined : end);
|
||||
})();
|
||||
|
||||
console.log('mobileAddList:');
|
||||
|
||||
test('the standing Add List row is gone', () => {
|
||||
// addListForm WAS that row. Mobile mode was its last caller.
|
||||
assert.ok(!/\+addListForm/.test(swim), 'nothing renders the old row');
|
||||
assert.ok(!/template\(name="addListForm"\)/.test(swim), 'and the template is gone');
|
||||
assert.ok(!/Template\.addListForm\./.test(swimJs),
|
||||
'nor may a handler be left on it - that throws at module load');
|
||||
});
|
||||
|
||||
test('the + is in the list header, between the caret and the drag handle', () => {
|
||||
const caret = miniBranch.indexOf('fa-caret-right');
|
||||
const plus = miniBranch.indexOf('js-add-list-here');
|
||||
const handle = miniBranch.indexOf('js-list-handle');
|
||||
assert.ok(caret > -1 && plus > -1 && handle > -1,
|
||||
'caret, add-list and handle must all be in the mobile list row');
|
||||
assert.ok(caret < plus, 'the + comes after the caret');
|
||||
assert.ok(plus < handle, 'and before the drag handle');
|
||||
});
|
||||
|
||||
test('it is the same button as on desktop, not a lookalike', () => {
|
||||
// Same class -> same click handler, same behaviour (open the inline composer after
|
||||
// this list).
|
||||
assert.ok(/a\.js-add-list-here/.test(miniBranch), 'same class as the desktop button');
|
||||
const js = read('client/components/lists/listHeader.js');
|
||||
assert.ok(/'click \.js-add-list-here'/.test(js), 'and that class has a handler');
|
||||
// Same icon, which is what was asked for.
|
||||
const desktop = listHeader.slice(listHeader.indexOf('else if currentUser.isBoardMember'));
|
||||
const iconOf = src => /js-add-list-here[\s\S]{0,120}?i\.fa\.(fa-[\w-]+)/.exec(src);
|
||||
const mobileIcon = iconOf(miniBranch);
|
||||
const desktopIcon = iconOf(desktop);
|
||||
assert.ok(mobileIcon && desktopIcon, 'both must declare an icon');
|
||||
assert.strictEqual(mobileIcon[1], desktopIcon[1],
|
||||
'mobile must use the same Font Awesome icon as desktop');
|
||||
});
|
||||
|
||||
test('the grid has a column for it and the caret moves left', () => {
|
||||
const header = /\.mobile-mode \.list-header \{([^}]*)\}/.exec(css);
|
||||
assert.ok(header, 'the mobile list header must be styled');
|
||||
assert.ok(/grid-template-columns:\s*30px 1fr auto auto auto/.test(header[1]),
|
||||
'five columns: title, caret, +, handle');
|
||||
const col = (sel) => {
|
||||
const rule = new RegExp(`\\.mobile-mode \\.list-header \\.${sel} \\{([^}]*)\\}`).exec(css);
|
||||
assert.ok(rule, `${sel} must be placed`);
|
||||
return /grid-column:\s*(\d+)/.exec(rule[1])[1];
|
||||
};
|
||||
assert.strictEqual(col('list-header-menu-icon'), '3', 'caret in column 3');
|
||||
assert.strictEqual(col('list-header-add-list'), '4', 'the + between them, column 4');
|
||||
assert.strictEqual(col('list-header-handle'), '5', 'handle last, column 5');
|
||||
});
|
||||
|
||||
test('the row mirrors in RTL', () => {
|
||||
// A grid lays out along the INLINE axis, so column 1 is on the right under
|
||||
// dir="rtl" and the row mirrors by itself - as long as nothing pins a physical
|
||||
// side. A single `left:`/`right:` here would strand a control on the wrong edge.
|
||||
for (const sel of ['list-header-menu-icon', 'list-header-add-list', 'list-header-handle']) {
|
||||
const rule = new RegExp(`\\.mobile-mode \\.list-header \\.${sel} \\{([^}]*)\\}`).exec(css)[1];
|
||||
assert.ok(!/(^|[\s;])(left|right)\s*:/.test(rule),
|
||||
`${sel} must not use a physical left/right - it would not mirror in RTL`);
|
||||
}
|
||||
});
|
||||
|
||||
test('an empty swimlane and an empty board keep a + of their own', () => {
|
||||
// There is no list header to host the button when there are no lists, so without
|
||||
// this the first list could never be created on a phone.
|
||||
const mini = swim.slice(swim.indexOf('if isMiniScreen'), swim.indexOf('template(name="listsGroup")'));
|
||||
assert.ok(/if swimlaneHasNoLists[\s\S]{0,400}js-open-empty-add-list/.test(mini),
|
||||
'an empty swimlane shows its own + in mobile mode');
|
||||
const listsGroupMini = swim.slice(swim.indexOf('template(name="listsGroup")'));
|
||||
assert.ok(/if boardHasNoLists[\s\S]{0,400}js-open-empty-add-list/.test(listsGroupMini),
|
||||
'and so does an empty board in Lists view');
|
||||
});
|
||||
|
||||
test('the composer opens after the list whose + was clicked', () => {
|
||||
// Same mechanism as desktop: the click records the list id, and the composer is
|
||||
// rendered after that list.
|
||||
const mini = swim.slice(swim.indexOf('if isMiniScreen'), swim.indexOf('template(name="listsGroup")'));
|
||||
assert.ok(/\+miniList\(this\)\s*\n\s*if isAddListAfter _id\s*\n\s*\+addListInline/.test(mini),
|
||||
'the inline composer is rendered after the list it belongs to');
|
||||
assert.ok(/isAddListAfter\(listId\)/.test(swimJs), 'and the helper still exists');
|
||||
});
|
||||
|
||||
console.log(`\nmobileAddList: ${passed} tests passed`);
|
||||
@@ -37,9 +37,11 @@ test('#6465: header add-list button + inline composer template exist', () => {
|
||||
|
||||
test('#6465: the standing leading Add List column is replaced by empty-state gating', () => {
|
||||
const swim = read('client/components/swimlanes/swimlanes.jade');
|
||||
// The non-mini swimlane + listsGroup branches now only show the composer for an
|
||||
// empty swimlane/board (swimlaneHasNoLists / boardHasNoLists), instead of an
|
||||
// unconditional leading +addListForm column. (The mini-screen path is unchanged.)
|
||||
// Every branch - mini and non-mini, swimlane and listsGroup - now shows the composer
|
||||
// only for an empty swimlane/board (swimlaneHasNoLists / boardHasNoLists), instead of
|
||||
// an unconditional leading Add List column (desktop) or row (mobile). Otherwise it is
|
||||
// opened from a list header + . The addListForm template that rendered that standing
|
||||
// column/row is gone.
|
||||
assert.ok(/if swimlaneHasNoLists/.test(swim) && /if boardHasNoLists/.test(swim),
|
||||
'non-mini branches gate the composer on the empty state');
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user