@topconsultnpm/sdkui-react-beta 6.14.144 → 6.14.145

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -301,7 +301,7 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
301
301
  return (_jsxs("div", { style: { width: '100%' }, children: [documentData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.DocumentData, children: documentData.map(item => renderMetadataItem(item)) }), workItemData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.WorkItemData, children: workItemData.map(item => renderMetadataItem(item, true)) }), technicalWorkItemData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.WorkItemTechnicalData, defaultCollapsed: true, children: technicalWorkItemData.map(item => renderMetadataItem(item, true)) })] }));
302
302
  };
303
303
  const layoutDraft = () => {
304
- // Definiamo l'ordine desiderato per gli elementi in draftData
304
+ // Definiamo l'ordine desiderato per gli elementi
305
305
  const desiredDraftOrder = [
306
306
  DraftsMIDs.Name,
307
307
  DraftsMIDs.Description,
@@ -312,10 +312,14 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
312
312
  SystemMIDsAsNumber.LastUpdateTime,
313
313
  SystemMIDsAsNumber.CreationTime,
314
314
  ];
315
+ const desiredCICOOrder = [
316
+ DraftsMIDs.CheckOutDate,
317
+ DraftsMIDs.CheckOutUserID,
318
+ ];
315
319
  // Usiamo una mappa temporanea per raccogliere gli elementi per ID,
316
320
  // in modo da poterli poi ordinare facilmente.
317
321
  const tempDraftDataMap = {};
318
- const checkOutData = [];
322
+ const tempCICODataMap = {};
319
323
  metadataValues.forEach(item => {
320
324
  switch (item.md?.id) {
321
325
  case DraftsMIDs.Name:
@@ -328,23 +332,29 @@ const TMMetadataValues = ({ showCheckBoxes = ShowCheckBoxesMode.Never, checkPerm
328
332
  case SystemMIDsAsNumber.FileSize:
329
333
  case SystemMIDsAsNumber.LastUpdateTime:
330
334
  case SystemMIDsAsNumber.CreationTime:
331
- item.isReadOnly = true;
335
+ // item.isReadOnly = true;
332
336
  tempDraftDataMap[item.md.id] = item;
333
337
  break;
334
338
  case DraftsMIDs.CheckOutDate:
335
339
  case DraftsMIDs.CheckOutUserID:
336
- checkOutData.push(item);
340
+ tempCICODataMap[item.md.id] = item;
337
341
  break;
338
342
  default: break;
339
343
  }
340
344
  });
341
- // Ora creiamo l'array draftData finale nell'ordine desiderato
345
+ // Visualizziamo nell'ordine desiderato
342
346
  const draftData = [];
343
347
  desiredDraftOrder.forEach(id => {
344
348
  if (tempDraftDataMap[id]) {
345
349
  draftData.push(tempDraftDataMap[id]);
346
350
  }
347
351
  });
352
+ const checkOutData = [];
353
+ desiredCICOOrder.forEach(id => {
354
+ if (tempCICODataMap[id]) {
355
+ checkOutData.push(tempCICODataMap[id]);
356
+ }
357
+ });
348
358
  return (_jsxs("div", { style: { width: '100%' }, children: [draftData.length > 0 && _jsx(TMAccordion, { title: SDKUI_Localizator.Draft, children: draftData.map(item => renderMetadataItem(item)) }), checkOutData.length > 0 && _jsx(TMAccordion, { title: `${SDKUI_Localizator.CheckIn}/${SDKUI_Localizator.CheckOut}`, children: checkOutData.map(item => renderMetadataItem(item, true)) })] }));
349
359
  };
350
360
  const renderForm = () => {
@@ -467,9 +467,9 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
467
467
  const isMobile = deviceType === DeviceType.MOBILE;
468
468
  const isApprView = fromDTD?.templateTID === TemplateTIDs.WF_WIApprView;
469
469
  useEffect(() => {
470
- if (isApprView && !showAll)
470
+ if ((isApprView || TID === SystemTIDs.Drafts) && !showAll)
471
471
  setShowAll(true);
472
- }, [isApprView]);
472
+ }, [isApprView, TID]);
473
473
  const tmDcmtForm = useMemo(() => _jsx(_Fragment, { children: metadataValuesSource.length > 0 &&
474
474
  _jsxs(StyledToolbarCardContainer, { children: [_jsx(TMMetadataValues, { TID: TID, metadataValues: metadataValuesSource, metadataValuesOrig: metadataValuesSourceOrig, isExpertMode: isExpertMode, isOpenDistinctValues: isOpenDistinctValues, openChooserBySingleClick: !isOpenDistinctValues, selectedMID: focusedMetadataValue?.mid, isReadOnly: formMode === FormModes.ReadOnly, layoutMode: layoutMode, deviceType: deviceType, validationItems: validationItems, onFocusedItemChanged: (item) => { (item?.mid !== focusedMetadataValue?.mid) && setFocusedMetadataValue(item); }, onValueChanged: (newItems) => {
475
475
  setFormData((prevItems) => prevItems.map((item) => {
@@ -486,7 +486,7 @@ const TMDcmtForm = ({ showHeader = true, onSaveRecents, layoutMode = LayoutModes
486
486
  break;
487
487
  }
488
488
  } }), _jsxs(StyledFormButtonsContainer, { children: [_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10 }, children: _jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }, children: layoutMode === LayoutModes.Update ? _jsxs(_Fragment, { children: [_jsx(TMSaveFormButtonSave, { showTooltip: false, btnStyle: 'advanced', advancedColor: '#f09c0a', isModified: isModified, formMode: formMode, errorsCount: validationItems.filter(o => o.ResultType == ResultTypes.ERROR).length, onSaveAsync: confirmActionPopup }), _jsx(TMSaveFormButtonUndo, { btnStyle: 'toolbar', showTooltip: true, color: 'primary', isModified: isModified, formMode: formMode, onUndo: onUndoHandler })] }) :
489
- _jsxs(_Fragment, { children: [_jsx(TMButton, { disabled: archiveBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconBoxArchiveIn, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Archive, advancedColor: TMColors.success, onClick: confirmActionPopup }), _jsx(TMButton, { disabled: !clearFormBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconClear, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Clear, advancedColor: TMColors.tertiary, onClick: clearFormHandler }), DID && _jsx(TMButton, { disabled: undoBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconUndo, {}), width: '150px', showTooltip: false, caption: SDKUI_Localizator.Undo, advancedColor: TMColors.tertiary, onClick: onUndoHandler })] }) }) }), totalItems > listMaxItems && !isApprView && _jsx(TMShowAllOrMaxItemsButton, { showAll: showAll, dataSourceLength: totalItems, onClick: () => { setShowAll(!showAll); } })] }), _jsx(ConfirmAttachmentsDialog, {})] }) }), [TID, DID, formData, formDataOrig, dcmtFile, focusedMetadataValue, isOpenDistinctValues, isOpenFormulaEditor, validationItems, showAll, inputFile]);
489
+ _jsxs(_Fragment, { children: [_jsx(TMButton, { disabled: archiveBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconBoxArchiveIn, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Archive, advancedColor: TMColors.success, onClick: confirmActionPopup }), _jsx(TMButton, { disabled: !clearFormBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconClear, {}), width: 'auto', showTooltip: false, caption: SDKUI_Localizator.Clear, advancedColor: TMColors.tertiary, onClick: clearFormHandler }), DID && _jsx(TMButton, { disabled: undoBtnDisabled, btnStyle: 'advanced', icon: _jsx(IconUndo, {}), width: '150px', showTooltip: false, caption: SDKUI_Localizator.Undo, advancedColor: TMColors.tertiary, onClick: onUndoHandler })] }) }) }), totalItems > listMaxItems && !isApprView && TID !== SystemTIDs.Drafts && _jsx(TMShowAllOrMaxItemsButton, { showAll: showAll, dataSourceLength: totalItems, onClick: () => { setShowAll(!showAll); } })] }), _jsx(ConfirmAttachmentsDialog, {})] }) }), [TID, DID, formData, formDataOrig, dcmtFile, focusedMetadataValue, isOpenDistinctValues, isOpenFormulaEditor, validationItems, showAll, inputFile]);
490
490
  const tmBlog = useMemo(() => _jsx(TMDcmtBlog, { tid: TID, did: DID }), [TID, DID]);
491
491
  const tmSysMetadata = useMemo(() => _jsx(TMMetadataValues, { layoutMode: layoutMode, openChooserBySingleClick: !isOpenDistinctValues, TID: TID, isReadOnly: true, deviceType: deviceType, metadataValues: formData.filter(o => (o.mid != undefined && o.mid <= 100)), metadataValuesOrig: formData.filter(o => (o.mid != undefined && o.mid <= 100)), validationItems: [] }), [TID, layoutMode, formData, deviceType]);
492
492
  const tmDcmtPreview = useMemo(() => _jsx(TMDcmtPreviewWrapper, { currentDcmt: currentDcmt, dcmtFile: dcmtFile ?? inputFile, deviceType: deviceType, fromDTD: fromDTD, layoutMode: layoutMode, onFileUpload: (setFile) => {
@@ -325,7 +325,7 @@ const TMWGsCopyMoveForm = (props) => {
325
325
  const cellIconRender = useCallback((cellData) => {
326
326
  const data = cellData.data;
327
327
  const tooltipContent = (_jsxs("div", { style: { textAlign: 'left' }, children: [_jsx("div", { children: _jsx("strong", { children: SDKUI_Localizator.WorkGroup }) }), _jsx("hr", {}), _jsxs("div", { children: [_jsx("span", { style: { fontWeight: 'bold' }, children: "ID:" }), " ", data.id] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.Name, ":"] }), " ", data.name ?? '-'] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.OwnerName, ":"] }), " ", data.ownerName ?? '-'] }), _jsxs("div", { children: [_jsxs("span", { style: { fontWeight: 'bold' }, children: [SDKUI_Localizator.CreationTime, ":"] }), " ", Globalization.getDateTimeDisplayValue(data.creationTime)] })] }));
328
- return _jsx(TMTooltip, { content: tooltipContent, children: _jsx(IconUserGroup, { color: TMColors.primary }) });
328
+ return _jsx(TMTooltip, { content: tooltipContent, children: _jsx(IconUserGroup, { color: "#009700" }) });
329
329
  }, []);
330
330
  const cellDafaultRender = useCallback((cellData) => {
331
331
  return _jsx("span", { children: cellData.value });
@@ -64,7 +64,6 @@ export const WorkFlowOperationButtons = ({ deviceType = DeviceType.DESKTOP, appr
64
64
  export const WorkFlowApproveRejectPopUp = ({ TID = 0, DID = 0, deviceType = DeviceType.DESKTOP, isReject, selectedItems = [], onClose, onCompleted }) => {
65
65
  const [commentValue, setCommentValue] = useState('');
66
66
  const disable = commentValue.length === 0;
67
- const count = () => { return selectedItems.length.toString() + ' Workitem'; };
68
67
  const completeOrRejectAsync = async (isReject) => {
69
68
  try {
70
69
  TMSpinner.show();
@@ -89,18 +88,17 @@ export const WorkFlowApproveRejectPopUp = ({ TID = 0, DID = 0, deviceType = Devi
89
88
  }
90
89
  };
91
90
  const workflowAction = isReject === 0 ? SDKUI_Localizator.Approve : SDKUI_Localizator.Reject;
92
- const itemCount = selectedItems.length > 0 ? `(${count()})` : '';
93
- const title = `${workflowAction} workitem ${itemCount}`;
94
- return (_jsx(TMModal, { title: title, onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, children: _jsxs(StyledModalBodyWrapper, { children: [_jsxs(StyledModalContentContainer, { children: [_jsxs("p", { style: { color: (isReject === 1 && disable) ? TMColors.error : 'black' }, children: ["Commento ", isReject === 1 && disable && _jsx("span", { children: ' (Campo obbligatorio)' }), " "] }), _jsx(StyledTextArea, { "$isValid": isReject === 0 ? true : !disable, value: commentValue, onChange: (e) => setCommentValue(e.target.value) })] }), _jsx(StyledModalFooter, { children: isReject === 0
95
- ? _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconApply, {}), caption: 'Approva', disabled: false, onClick: () => completeOrRejectAsync(isReject), advancedColor: TMColors.success })
96
- : _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconCloseOutline, {}), caption: 'Rifiuta', disabled: disable, onClick: () => { !disable && completeOrRejectAsync(isReject); }, advancedColor: TMColors.error }) })] }) }));
91
+ const itemCount = selectedItems.length > 0 ? `(${selectedItems.length} workitem)` : '';
92
+ const title = `${workflowAction} ${itemCount}`;
93
+ return (_jsx(TMModal, { title: title, onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, children: _jsxs(StyledModalBodyWrapper, { children: [_jsxs(StyledModalContentContainer, { children: [_jsxs("p", { style: { color: (isReject === 1 && disable) ? TMColors.error : 'black' }, children: [SDKUI_Localizator.CommentText, " ", isReject === 1 && disable && _jsx("span", { children: ' (Campo obbligatorio)' }), " "] }), _jsx(StyledTextArea, { "$isValid": isReject === 0 ? true : !disable, value: commentValue, onChange: (e) => setCommentValue(e.target.value) })] }), _jsx(StyledModalFooter, { children: isReject === 0
94
+ ? _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconApply, {}), caption: SDKUI_Localizator.Approve, disabled: false, onClick: () => completeOrRejectAsync(isReject), advancedColor: TMColors.success })
95
+ : _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconCloseOutline, {}), caption: SDKUI_Localizator.Reject, disabled: disable, onClick: () => { !disable && completeOrRejectAsync(isReject); }, advancedColor: TMColors.error }) })] }) }));
97
96
  };
98
97
  export const WorkFlowReAssignPopUp = ({ DID = 0, TID = 0, deviceType = DeviceType.DESKTOP, onClose, selectedItems = [], onCompleted }) => {
99
98
  const [commentValue, setCommentValue] = useState('');
100
99
  const [selectedUserID, setSelectedUserID] = useState([]);
101
100
  const [participants, setParticipants] = useState([]);
102
101
  const disable = commentValue.length === 0 || !selectedUserID;
103
- const count = () => { return selectedItems.length.toString() + ' workitem'; };
104
102
  const reAssignWorkFlowAsync = async () => {
105
103
  try {
106
104
  TMSpinner.show();
@@ -153,9 +151,9 @@ export const WorkFlowReAssignPopUp = ({ DID = 0, TID = 0, deviceType = DeviceTyp
153
151
  fetchData();
154
152
  return () => { isMounted = false; };
155
153
  }, [tidToUse]);
156
- return (_jsx(TMModal, { onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, title: SDKUI_Localizator.WorkitemReassign + (selectedItems.length > 0 ? ' (' + count() + ')' : ''), children: _jsxs(StyledModalBodyWrapper, { children: [_jsxs(StyledModalContentContainer, { children: [_jsx(TMUserChooser, { dataSource: participants, values: selectedUserID, onValueChanged: (IDs) => {
154
+ return (_jsx(TMModal, { onClose: onClose, width: deviceType === DeviceType.MOBILE ? '95%' : '60%', height: '60%', isModal: true, title: SDKUI_Localizator.Reassign + (selectedItems.length > 0 ? ' (' + selectedItems.length + ' workitem)' : ''), children: _jsxs(StyledModalBodyWrapper, { children: [_jsxs(StyledModalContentContainer, { children: [_jsx(TMUserChooser, { dataSource: participants, values: selectedUserID, onValueChanged: (IDs) => {
157
155
  setSelectedUserID(IDs ?? []);
158
- } }), _jsxs("p", { style: { color: commentValue.length === 0 ? TMColors.error : 'black' }, children: ["Commento ", commentValue.length === 0 && _jsx("span", { children: ' (Campo obbligatorio)' }), " "] }), _jsx(StyledTextArea, { "$isValid": commentValue.length !== 0, value: commentValue, onChange: (e) => setCommentValue(e.target.value) })] }), _jsx(StyledModalFooter, { children: _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: disable, onClick: () => !disable && reAssignWorkFlowAsync(), advancedColor: TMColors.tertiary }) })] }) }));
156
+ } }), _jsxs("p", { style: { color: commentValue.length === 0 ? TMColors.error : 'black' }, children: [SDKUI_Localizator.CommentText, " ", commentValue.length === 0 && _jsx("span", { children: ' (Campo obbligatorio)' }), " "] }), _jsx(StyledTextArea, { "$isValid": commentValue.length !== 0, value: commentValue, onChange: (e) => setCommentValue(e.target.value) })] }), _jsx(StyledModalFooter, { children: _jsx(TMButton, { btnStyle: 'advanced', showTooltip: false, icon: _jsx(IconUser, { fontSize: 16 }), caption: SDKUI_Localizator.Reassign, disabled: disable, onClick: () => !disable && reAssignWorkFlowAsync(), advancedColor: TMColors.tertiary }) })] }) }));
159
157
  };
160
158
  export const WorkFlowMoreInfoPopUp = ({ DID = 0, TID = 0, deviceType = DeviceType.DESKTOP, onClose, onCompleted }) => {
161
159
  const [users, setUsers] = useState([]);
@@ -76,6 +76,7 @@ export declare class SDKUI_Localizator {
76
76
  static get Comment(): string;
77
77
  static get CommentAndComplete(): string;
78
78
  static get CommentDoesNotMeetRequirements(): "Der Kommentar erfüllt nicht die erforderlichen Anforderungen" | "The comment does not meet the required criteria" | "El comentario no cumple con los requisitos requeridos" | "Le commentaire ne répond pas aux exigences requises" | "O comentário não atende aos requisitos exigidos" | "Il commento non rispetta i requisiti richiesti";
79
+ static get CommentText(): string;
79
80
  static get CompleteError(): "Kompletter Fehler" | "Complete error" | "Error completo" | "Erreur complète" | "Erro completo" | "Errore completo";
80
81
  static get Configure(): "Konfigurieren" | "Configure" | "Configurar" | "Configura";
81
82
  static get Confirm(): string;
@@ -709,6 +709,16 @@ export class SDKUI_Localizator {
709
709
  default: return "Il commento non rispetta i requisiti richiesti";
710
710
  }
711
711
  }
712
+ static get CommentText() {
713
+ switch (this._cultureID) {
714
+ case CultureIDs.De_DE: return "Kommentar";
715
+ case CultureIDs.En_US: return "Comment";
716
+ case CultureIDs.Es_ES: return "Comentario";
717
+ case CultureIDs.Fr_FR: return "Commentaire";
718
+ case CultureIDs.Pt_PT: return "Comentário";
719
+ default: return "Commento";
720
+ }
721
+ }
712
722
  static get CompleteError() {
713
723
  switch (this._cultureID) {
714
724
  case CultureIDs.De_DE: return "Kompletter Fehler";
@@ -1,6 +1,6 @@
1
- import { AccessLevels, MetadataDataDomains, DcmtTypeListCacheService, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, QueryFunctions, SelectItem, SelectItemVisibilities, FromItem, LayoutModes, QueryOperators, SavedQueryDescriptor, SearchEngine, WhereItem, OrderByItem, SDK_Globals, AppModules } from '@topconsultnpm/sdk-ts-beta';
1
+ import { AccessLevels, MetadataDataDomains, DcmtTypeListCacheService, SystemMIDsAsNumber, MetadataDataTypes, QueryDescriptor, QueryFunctions, SelectItem, SelectItemVisibilities, FromItem, LayoutModes, QueryOperators, SavedQueryDescriptor, SearchEngine, WhereItem, OrderByItem, SDK_Globals, AppModules, SystemTIDs } from '@topconsultnpm/sdk-ts-beta';
2
2
  import { DateDisplayTypes, Globalization } from './Globalization';
3
- import { MetadataValueDescriptorEx } from '../ts';
3
+ import { DraftsMIDs, MetadataValueDescriptorEx } from '../ts';
4
4
  import { SDKUI_Localizator } from './SDKUI_Localizator';
5
5
  export const getTIDsByQd = (qd) => {
6
6
  let tids = [];
@@ -223,6 +223,39 @@ export const searchResultToMetadataValues = (tid, dtd, rows, mids, metadata, lay
223
223
  mvd.value = (md.dataDomain === MetadataDataDomains.Numerator && layoutMode === LayoutModes.Ark) ? undefined : value;
224
224
  mvd.isRequired = isNumeratorInArk || hasDefaultValue ? '0' : md.isRequired?.toString();
225
225
  mvd.isLexProt = isLexProt;
226
+ // Tipo documento SYSTEM - Bozze, imposta sola lettura
227
+ if (tid === SystemTIDs.Drafts) {
228
+ switch (mvd.mid) {
229
+ case DraftsMIDs.Ver:
230
+ case DraftsMIDs.LastVer:
231
+ mvd.isReadOnly = true;
232
+ break;
233
+ case DraftsMIDs.CheckOutDate:
234
+ mvd.customName = SDKUI_Localizator.ExtractedOn;
235
+ mvd.isReadOnly = true;
236
+ break;
237
+ case DraftsMIDs.CheckOutUserID:
238
+ mvd.customName = SDKUI_Localizator.ExtractedBy;
239
+ mvd.isReadOnly = true;
240
+ break;
241
+ case SystemMIDsAsNumber.FileExt:
242
+ mvd.customName = SDKUI_Localizator.Extension;
243
+ mvd.isReadOnly = true;
244
+ break;
245
+ case SystemMIDsAsNumber.FileSize:
246
+ mvd.customName = SDKUI_Localizator.File_Size;
247
+ mvd.isReadOnly = true;
248
+ break;
249
+ case SystemMIDsAsNumber.LastUpdateTime:
250
+ mvd.customName = SDKUI_Localizator.LastUpdateTime;
251
+ mvd.isReadOnly = true;
252
+ break;
253
+ case SystemMIDsAsNumber.CreationTime:
254
+ mvd.customName = SDKUI_Localizator.CreationTime;
255
+ mvd.isReadOnly = true;
256
+ break;
257
+ }
258
+ }
226
259
  return mvd;
227
260
  };
228
261
  metadata.forEach(md => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.14.144",
3
+ "version": "6.14.145",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",