What is the syntax to return/declare a pair with Closure - google-closure-compiler

I don't manage to find the return type for a pair when using the Closure Compiler.
The best I found far is !Array but I can't find how to specify that the first element is a number and the second is a boolean.
Also I can't find how to declare a pair. If function2 works well, in function3, whatever I'm trying leads to this warning:
WARNING - [JSC_MISPLACED_ANNOTATION] Misplaced type annotation. Type annotations are not allowed here. Are you missing parentheses?
I'm using Closure Compiler Version: v20220301 with options -warning_level VERBOSE --compilation_level ADVANCED
var ABCD = ABCD || {};
/**
* #param {number} param1
* #param {boolean} param2
* #return {!Array}
*/
ABCD.function1 = function(param1, param2)
{
return [param1, param2];
}
ABCD.function2 = function()
{
/** #type {number} */
let var1 = 0;
/** #type {boolean} */
let var2 = false;
[var1, var2] = ABCD.function1(0, false);
}
ABCD.function3 = function()
{
/** #type {*} */
let [param1, param2] = ABCD.function1(0, false);
}

Related

write phpunit test function for a service class

Hi guys i am trying to create a phpunit test for below function.
/**
* Get file size
*
* #param string $filePath
* #return int
*/
public function getFileSize(string $filePath): int
{
if (!file_exists($filePath)) {
return 0;
}
return filesize($filePath);
}
So far i have tried like this
/**
* Test get file size with invalid data
*/
public function testGetFileSizeWithValidData()
{
$filePath = 'rgreherher';
$service = new Tickets_Service_ContactMomentVehicle();
$result = $service->getFileSize($filePath);
$this->assertSame($result, $filePath);
}
So when i run in my terminal i am getting error as
<string:rgreherher> does not match expected type "integer".
Can anyone help me what mistake i have done.
Thanks in advance.
The error is telling you exactly what is going on, you are comparing an integer ($result) to a string ($filePath).
If I understand your test case correctly, you should replace $filePath with $filePath's size instead.
public function testGetFileSizeWithValidData()
{
$filePath = 'rgreherher';
$filePathSize = 55; // actual file size of $filePath
$service = new Tickets_Service_ContactMomentVehicle();
$result = $service->getFileSize($filePath);
$this->assertSame($result, $filePathSize);
}
In your test you assume that the file exists, but if not you must remember that php function filesize (which is in your function getFileSize) returns false and generates an E_WARNING if there is no file.

Annotate JSON object as array for Google Closure Compiler

I need to annotate a JSON object as an array but I can't get it to work:
/** #type {Array} */
let resp = JSON.parse(response);
for (let item of resp) {
}
The Closure Compiler returns:
WARNING - initializing variable found : * required: (Array|null)
let resp = JSON.parse(response);
^^^^^^^^^^^^^^^^^^^^
Because a JSON parse can return almost anything, you have to type cast the result:
let resp = /** #type {Array} */ (JSON.parse(response));
Note the extra parenthesis
You might consider adding the type of items in the array:
/** #type {Array<string>} */

Error With Email Change Notifications Using Project Tracker Template

Short Version:
I am using the code from the Project Tracker Template to send out emails showing the change in a status for a field (Contact Name changed from: Billy -> Susan).
Everything works perfectly expect when I have a field that is a Date instead of a String. If I have a Date field in the code I get the following error:
'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object. Error: 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object. at onSystemOrdersSave_ (Datasources:218) at models.SystemOrders.onSaveEvent:1
Modifying records: (Error) : 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object.
(Error) : 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object.
Any help would be greatly appreciated!
Long Version
I am using the code below (adjusted to fit the names of my models and fields).
Whenever I add a Date field (ex: DeliveryDate) to the function "notifyAboutItemChanges_" function and the "onSystemOrdersSave_" function I get the error about "expecting a string, but found an object".
Note: The OldValue and NewValue fields in the "History" model are both Strings.
Notifications Server Script:
/**
* Sends email.
* #param {!string} to - email address of a recipient.
* #param {!string} subject - subject of email message.
* #param {!string} body - body of email message.
*/
function sendEmail_(to, subject, body) {
try {
MailApp.sendEmail({
to: to,
subject: subject,
htmlBody: body,
noReply: true
});
} catch (e) {
// Suppressing errors in email sending because email notifications
// are not critical for the functioning of the app.
console.error(JSON.stringify(e));
}
}
/**
* Sends email notification about recent project item changes to item owner
* and assignee.
* #param {!Array<ItemHistory>} changes - list of recent project item changes.
*/
function notifyAboutItemChanges_(changes) {
if (!changes || changes.length < 2) {
return;
}
var settings = getAppSettingsRecord_()[0];
if (!settings.EnableEmailNotifications) {
return;
}
var data = {
appUrl: settings.AppUrl,
itemShowName: changes[0].ShowName,
itemUsersPosition: changes[0].UsersPosition,
itemDeliveryInfo: changes[0].DeliveryInfo,
itemDeliveryDate: changes[0].DeliveryDate,
itemKey: changes[0]._key,
itemName: changes[0].Name,
modifiedBy: changes[0].ModifiedBy,
changes: changes
};
// Email subject.
var subjectTemplate =
HtmlService.createTemplate(settings.NotificationEmailSubject);
subjectTemplate.data = data;
var subject = subjectTemplate.evaluate().getContent();
// Email body.
var emailTemplate =
HtmlService.createTemplate(settings.NotificationEmailBody);
emailTemplate.data = data;
var htmlBody = emailTemplate.evaluate().getContent();
sendEmail_('user#gmail.com', subject, htmlBody);
Datasources Server Script:
/**
* Item key URL parameter.
*/
var ITEM_KEY = 'itemKey';
/**
* Checks that Application Settings record already exists.
* Otherwise creates a new one.
* #return {!Array<AppSettings>} app settings record as an array.
*/
function getAppSettingsRecord_() {
var newQuery = app.models.AppSettings.newQuery();
var settingsRecords = newQuery.run();
if (settingsRecords.length > 1) {
console.warn('There is more than one(%s) App Settings entries' +
'in the database', settingsRecords.length);
}
if (settingsRecords.length === 0) {
var settingsRecord = app.models.AppSettings.newRecord();
settingsRecord.AppUrl = ScriptApp.getService().getUrl();
settingsRecord.NotificationEmailSubject =
'A change has been made to <?= data.itemShowName?>: <?= data.itemUsersPosition?>';
settingsRecord.NotificationEmailBody =
'Hello!\n<br/>\n<p><b><?= data.modifiedBy ?></b> ' +
'made the following changes: </p>\n' +
'<? for (var i = 1; i < data.changes.length; i++) {\n' +
'\tvar change = data.changes[i]; ?>\n' +
'\t<b><?= change.FieldName ?>: </b>\n' +
'\t<? if (change.FieldName === "Comment") { ?>\n' +
'\t\t<div style="white-space: pre-line;"><?= change.NewValue ?></div>' +
'\n\t<? } else { ?>\n ' +
'\t\t<?= change.OldValue ?> → <?= change.NewValue ?>' +
'\n\t<? } ?>\n\t<br/>\n' +
'<? } ?>\n<br/>\n' +
'<a href="<?= data.appUrl ?>?' + ITEM_KEY + '=<?= data.itemKey ?>' +
'#EditItem" target="_blank">Go to the project item</a>';
app.saveRecords([settingsRecord]);
return [settingsRecord];
} else {
return settingsRecords;
}
}
/**
* Populates project record with required data on project create event.
* #param {!Project} project - project being created.
*/
function onProjectCreate_(project) {
var date = new Date();
project.CreatedDate = date;
project.ModifiedDate = date;
project.ModifiedBy = currentUserEmail_();
}
/**
* Audits project on changes.
* #param {!Project} project - project being modified.
*/
function onProjectSave_(project) {
project.ModifiedDate = new Date();
project.ModifiedBy = currentUserEmail_();
}
/**
* Populates project item with required data on item create event, adds
* comment entry to the project item history.
* #param {!SystemOrders} SystemOrders - project item being created.
*/
function onSystemOrdersCreate_(SystemOrders) {
var date = new Date();
var editor = currentUserEmail_();
if (SystemOrders.Comment) {
SystemOrders.Comment = SystemOrders.Comment.trim();
}
SystemOrders.CreatedDate = date;
SystemOrders.Owner = editor;
SystemOrders.ModifiedDate = date;
SystemOrders.ModifiedBy = editor;
if (SystemOrders.Comment) {
var history = app.models.SystemOrdersHistory.newRecord();
history.CreatedBy = currentUserEmail_();
history.CreatedDate = new Date();
history.FieldName = 'Comment';
history.NewValue = SystemOrders.Comment;
app.saveRecords([history]);
SystemOrders.History.push(history);
}
}
/**
* Calculates history entries sum for {Array<SystemOrders>}.
* #param {!number} historySum - the accumulated number of history entries
* previously returned in the last invocation of the callback, or
* initialValue, if supplied.
* #param {!SystemOrders} SystemOrders - the current {SystemOrders} being
* processed in the array.
* #return {!number} history entries sum.
*/
function sumHistory_(historySum, SystemOrders) {
return historySum + SystemOrders.History.length;
}
/**
* Calculates potential project deletion impact.
* Throws an error if there is no project with the key provided.
* #param {!string} projectKey - project key to calculate deletion impact.
*/
function getDeleteProjectImpact(projectKey) {
var projectQuery = app.models.Project.newQuery();
projectQuery.prefetch.Items._add();
projectQuery.prefetch.Items.History._add();
projectQuery.filters._key._equals = projectKey;
var projects = projectQuery.run();
if (projects.length === 0) {
throw new Error('Project with key ' + projectKey + ' was not found.');
}
var SystemOrderss = projects[0].Items;
return {
affectedItems: SystemOrderss.length,
affectedHistory: SystemOrderss.reduce(sumHistory_, 0)
};
}
/**
* Checks that project item readonly fields were not modified.
* Throws an error if user attempts to modify read only fields.
* #param {!SystemOrders} record - modified project item.
* #param {!SystemOrders} oldRecord - project item before modification.
*/
function validateItemChange_(record, oldRecord) {
var readonlyFields = [
'CreatedDate',
'ModifiedBy',
'ModifiedDate',
'Owner'
];
for (var i = 0; i < readonlyFields.length; i++) {
var field = readonlyFields[i];
var newValue = record[field];
var oldValue = oldRecord[field];
var isDate = newValue instanceof Date && oldValue instanceof Date;
if (isDate === true) {
newValue = record[field].getDate();
oldValue = oldRecord[field].getDate();
}
if (newValue === oldValue) {
continue;
}
throw new Error(field + ' field is read only');
}
}
/**
* Handles project item change event, creates history entries for each changed
* field.
* #param {!SystemOrders} record - modified project item.
* #param {!SystemOrders} oldRecord - project item before modification.
*/
function onSystemOrdersSave_(record, oldRecord) {
validateItemChange_(record, oldRecord);
var editableFields = [
'ShowName',
'UsersPosition',
'DeliveryInfo',
'DeliveryDate'
];
var editor = currentUserEmail_();
var date = new Date();
var changes = [record];
record.ModifiedBy = editor;
record.ModifiedDate = date;
for (var i = 0; i < editableFields.length; i++) {
var field = editableFields[i];
var newValue = record[field];
var oldValue = oldRecord[field];
if (newValue !== oldValue) {
var history = app.models.SystemOrdersHistory.newRecord();
history.Item = record;
history.CreatedBy = editor;
history.CreatedDate = date;
history.FieldName = field;
history.NewValue = newValue;
history.OldValue = oldValue;
changes.push(history);
}
}
app.saveRecords(changes);
notifyAboutItemChanges_(changes);
}
/**
* Counts project items by some grouping criteria(field).
* #param {!string} projectKey - project key to calculate stats.
* #param {!string} grouping - project item field to group items by.
* #param {!Array<string>} groupingValues - possible field values.
* #return {!Array<SystemOrderssBreakdown>} grouped project items counts.
*/
function getSystemOrderssBreakdown_(projectKey, grouping, groupingValues) {
if (!grouping || !groupingValues || groupingValues.length === 0) {
return [];
}
var itemsQuery = app.models.SystemOrders.newQuery();
itemsQuery.prefetch.Project._add();
itemsQuery.filters.Project._key._equals = projectKey;
var items = itemsQuery.run();
if (items.length === 0) {
return [];
}
var records = [];
var map = {};
for (var i = 0; i < items.length; i++) {
var itemGrouping = items[i][grouping];
if (!map[itemGrouping]) {
map[itemGrouping] = 0;
}
map[itemGrouping]++;
}
for (i = 0; i < groupingValues.length; i++) {
var breakdownRecord = app.models.SystemOrderssBreakdown.newRecord();
var groupingValue = groupingValues[i];
breakdownRecord.Grouping = groupingValue;
breakdownRecord.ItemsCount = map[groupingValue] || 0;
records.push(breakdownRecord);
}
return records;
}
It fails here:
// history.NewValue and history.OldValue are strings
// newValue and oldValue can be of any type (Boolean, Number, Date,
// but not a relation as of now)
// You are getting an exception because you are not casting types
history.NewValue = newValue;
history.OldValue = oldValue;
You can fix it by adding fields of each possible type to your history model (NewStringValue, NewDateValue, NewBoolValue, NewNumberValue, OldStringValue...). With that approach you'll get benefits of strong typing, but your code and UI will become significantly more complex...
You can store all your fields' history as strings(like you are doing now), but in this case you'll need to think about formatting and localization in advance:
function fieldToString(field, fieldValue) {
// TODO: pass field metadata to individually handle
// different data types.
return fieldValue !== null ? fieldValue.toString() : null;
}
...
history.NewValue = fieldToString(field, newValue);
history.OldValue = fieldToString(field, oldValue);
...

Reactive variables on Meteor

I've been looking for reactive vars and dependencies on Meteor but I'm facing a problem today with an object.
The goal is to do a stepbar and display the changes once a page is loaded.
I defined an helper Stepbar_helper.js as below :
/**
* stepBar
*
* Stepbar library
*
*/
function StepBar(){
this.stepList = [];
this.curStep = 0; //current step
}
/* architecture of a step
var step = {
status : '',
title : '',
text : ''
}
*/
/**
* addStep
*
* Add a step to the list
*
* #param Array Properties of the step #see _props
*/
StepBar.prototype.addStep = function( properties) {
this.stepList.push(properties);
};
/**
* clear
*
* Clear the list of the steps
*
*/
StepBar.prototype.clear = function() {
this.stepList = [];
};
/**
* next
*
* Jump to the next step
*
*/
StepBar.prototype.next = function() {
if(this.curStep <= this.stepList.length){
this.curStep++;
//change attributes of previous
this.stepList[this.curStep-1].status = 'complete';
//set next to active
this.stepList[this.curStep].status = 'active';
}
};
/**
* getCurrent
*
* Returns the current element of the list
*
* #return Array
*/
StepBar.prototype.getCurrent = function() {
return (this.stepList[this.curStep] !== undefined) ? this.stepList[this.curStep] : null;
};
this.StepBar = new StepBar(); //export
Then I have a template :
<template name="stepbar">
<div class="row bs-wizard" style="border-bottom:0;">
{{#each Steps}}
<div class="col-xs-4 bs-wizard-step {{status}}">
<div class="text-center bs-wizard-stepnum">{{title}}</div>
<div class="progress"><div class="progress-bar"></div></div>
<div class="bs-wizard-info text-center">{{text}}</div>
</div>
{{/each}}
</div>
</template>
And finally I have a stepbar.js file :
//first step
StepBar.addStep({
status : 'active',
title : 'Step 1',
text : 'Propriétés'
});
StepBar.addStep({
status : 'disabled',
title : 'Step 2',
text : 'Paramètres'
});
StepBar.addStep({
status : 'disabled',
title : 'Step 3',
text : 'Aperçu'
});
Template.stepbar.helpers({
Steps: function () {
return StepBar.stepList;
}
});
The stepbar is called in another template, in the latter one when I click on a button I'm calling the method StepBar.next() which moves the current step to the next. However the template is not affected.
My question is where do I need to include Dependencies ? I have honestly no idea where neither how Deps works.
Regards
You can use ReactiveArray to create nicely reactive arrays. Install the package:
meteor add manuel:reactivearray
Then you should be able to replace your array with a ReactiveArray object, this way:
/**
* stepBar
*
* Stepbar library
*
*/
function StepBar(){
this.stepList = new ReactiveArray();
this.curStep = 0; //current step
}
/**
* clear
*
* Clear the list of the steps
*
*/
StepBar.prototype.clear = function() {
this.stepList.clear();
};
Since ReactiveArray behaves just like a normal array, all the rest of your class should work as is with this minor change. (forgive me if I'm wrong)
Then, just make sure you make your helper reactive using the list() method:
Template.stepbar.helpers({
Steps: function () {
return StepBar.stepList.list();
}
});

Alfresco media viewer web-preview extensibility

So I followed Will Abson's guide and source code for extending custom media viewers in Alfresco.
I have a couple of issues though.
I'm already using 4.2+ Alfresco, so no need to use head.ftl as deprecated, I'm using a second extensibility module to add my own configuration automatically, BUT:
how can I access the jsNode in my web-preview.get.js? Or better, is there a way to access properties values and aspects of a node which is being displayed?
I know about both server and client side var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef) and var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
which were mentioned in another question here, but it seems like except of default values like, mimeType, size, nodeRef, I'm not able to use those to get data from the file.
These are my changes:
web-preview.get.js in -config folder of my custom media-viewer
//<import resource="classpath:/alfresco/templates/org/alfresco/import/alfresco-util.js">
if (model.widgets)
{
for (var i = 0; i < model.widgets.length; i++)
{
var at = "test";
//var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
//var author = jsNode.properties["cm:author"];
var widget = model.widgets[i];
if (widget.id == "WebPreview")
{
var conditions = [];
// Insert new pluginCondition(s) at start of the chain
conditions.push({
attributes: {
mimeType: "application/pdf"
},
plugins: [{
name: "PDF",
attributes: {
}
}]
});
var oldConditions = eval("(" + widget.options.pluginConditions + ")");
// Add the other conditions back in
for (var j = 0; j < oldConditions.length; j++)
{
conditions.push(oldConditions[j]);
}
// Override the original conditions
model.pluginConditions = jsonUtils.toJSONString(conditions);
widget.options.pluginConditions = model.pluginConditions;
}
}
}
PDF.js
/**
* Copyright (C) 2014 Will Abson
*/
/**
* This is the "PDF" plug-in used to display documents directly in the web browser.
*
* Supports the "application/pdf" mime types.
*
* #namespace Alfresco.WebPreview.prototype.Plugins
* #class Alfresco.WebPreview.prototype.Plugins.PDF
*/
(function()
{
/**
* PDF plug-in constructor
*
* #param wp {Alfresco.WebPreview} The Alfresco.WebPreview instance that decides which plugin to use
* #param attributes {Object} Arbitrary attributes brought in from the <plugin> element
*/
Alfresco.WebPreview.prototype.Plugins.PDF = function(wp, attributes)
{
this.wp = wp;
this.attributes = YAHOO.lang.merge(Alfresco.util.deepCopy(this.attributes), attributes);
//this.wp.options.nodeRef = this.wp.nodeRef;
return this;
};
Alfresco.WebPreview.prototype.Plugins.PDF.prototype =
{
/**
* Attributes
*/
attributes:
{
/**
* Maximum size to display given in bytes if the node's content is used.
* If the node content is larger than this value the image won't be displayed.
* Note! This doesn't apply if src is set to a thumbnail.
*
* #property srcMaxSize
* #type String
* #default "2000000"
*/
srcMaxSize: "2000000"
},
/**
* Tests if the plugin can be used in the users browser.
*
* #method report
* #return {String} Returns nothing if the plugin may be used, otherwise returns a message containing the reason
* it cant be used as a string.
* #public
*/
report: function PDF_report()
{
// TODO: Detect whether Adobe PDF plugin is installed, or if navigator is Chrome
// See https://stackoverflow.com/questions/185952/how-do-i-detect-the-adobe-acrobat-version-installed-in-firefox-via-javascript
var srcMaxSize = this.attributes.srcMaxSize;
if (!this.attributes.src && srcMaxSize.match(/^\d+$/) && this.wp.options.size > parseInt(srcMaxSize))
{
return this.wp.msg("pdf.tooLargeFile", this.wp.options.name, Alfresco.util.formatFileSize(this.wp.options.size), Alfresco.util.formatFileSize(this.attributes.srcMaxSize));
}
},
/**
* Display the node.
*
* #method display
* #public
*/
display: function PDF_display()
{
// TODO: Support rendering the content of the thumbnail specified
var src = this.wp.getContentUrl();
var test = this.attributes.author;
//var test = this.wp.options.nodeRef;
//var jsNode = new Alfresco.util.Node(test);
//var jsNode = AlfrescoUtil.getNodeDetails(this.wp.options.nodeRef);
//var author = jsNode.properties["cm:author"];
//var test = this.wp.options.author;
//var test1 = this.wp.options.mimeType;
//var test = this.attributes.author.replace(/[^\w_\-\. ]/g, "");
//.replace(/[^\w_\-\. ]/g, "");
return '<iframe name="' + test + '" src="' + src + '"></iframe>';
}
};
})();
As you can see by commented sections I tried different methods to access node properties/values, even simple strings, but I'm missing something for sure.
Thanks.
If you take a look at the source code you'll see that the helper method is nothing else than doing a remote call to var url = '/slingshot/doclib2/node/' + nodeRef.replace('://', '/');
So take a look at what that Repository WebScript is returning en match it to the properties you need.
I normally don't use this one and I know for sure the /api/metadata returns all the properties.

Resources