Search tree node via closure in Google Apps Script - unit-testing

General problem I'm trying to solve
I'm trying to implement a search tree in Google Apps Script, sorted by pkgName attribute, with the end purpose of comparing imported metadata on a software project against a Sheet containing similar data.
To keep the namespace of the constructor function from being polluted with "private" properties, I used closures.
Implementation
The implementation I have thus far is thus:
SheetDataNode.gs
/**
* Constructor for a SheetDataNode. Takes one, three, or four arguments.
* #param { { package : string, files : { complexity : number, name : string, testingStatus : string }[], rowNumber : number } | string } line data or package name
* #param { string } filename : the files contained in package
* #param { number } complexity : the total number of branches in the file
* #param { number } rowNumber : the row number as this appears in the spreadsheet it is being created from
* #param { string } [ testingStatus ] : the status on the testing of this file. Should be one of the following: NOT_TESTED, FULLY_TESTED, IN_PROGRESS, or PARTIAL
* #returns { SheetDataNode }
* #deprecated This function is not working right now
**/
function SheetDataNode(data, filename, complexity, rowNumber, testingStatus) {
var _pkgName = '';
var _leftChild = null;
var _rightChild = null;
var _beenFound = false;
var _rowNumber = rowNumber;
var _files = [];
// if there's only one argument, it better be an object, having the required fields
if (arguments.length === 1) {
// it should have package field
if ((data.package === undefined) || (data.package !== data.package.toString())) {
throw ReferenceError('only one argument was specified, but it is not an object that contains package');
}
// it should have files field
if ((data.files === undefined) || (!Array.isArray(data.files))) {
throw ReferenceError('Called from the one-arg constructor, so files should be Array');
}
// that files field should itself be an object with the following fields: complexity and name
for (var idx in data.files) {
if (data.files[idx].complexity !== parseInt(data.files[idx].complexity)) {
throw TypeError("complexity should be an integer");
}
if (data.files[idx].name !== data.files[idx].name.toString()) {
throw TypeError("name of file should be a string");
}
}
// sort the array of files
data.files.sort(fileSorter)
// call the initialization function
return SheetDataNode._init(data.package, data.files, parseInt(data.rowNumber));
}
// performing argument checking
if (filename !== filename.toString()) throw TypeError("filename is supposed to be a String")
if ((complexity !== undefined) && (complexity !== parseInt(complexity))) {
throw TypeError("complexity must be a number, or undefined")
}
// call the initialization function, constructing a single file object
return SheetDataNode._init(data.toString(), [{
complexity : complexity,
name: filename,
testingStatus : testingStatus
}])
}
// Helper private function that performs initialization
SheetDataNode._init = function(package, files, rowNumber) {
// bring in the variables
var _pkgName = package;
var _files = files;
var _leftChild = null;
var _rightChild = null;
var _beenFound = false;
var _rowNumber = rowNumber;
// providing a function to add file
_addFile = function(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
return {
getRowNumber : function() { return _rowNumber; },
getPackageName : function () { return _pkgName; },
getFiles: function() { return _files; },
addFile : _addFile,
addFiles : function(files) {
if (!Array.isArray(files)) throw TypeError("files should be an Array")
for (var idx in files) {
_addFile(files[idx])
}
},
getLeftChild : function() { return _leftChild; },
setLeftChild : function(node) {
_leftChild = node;
},
getRightChild : function() { return _rightChild; },
setRightChild : function(node) {
_rightChild = node;
},
insertNode : function(node) {
// set the current node as the head node
var currentNode = this;
// while we are on a non-null node
while (currentNode) {
// if the package of node is the same as that of currentNode
if (currentNode.getPackageName() === node.getPackageName()) {
// simply add the files of node to currentNode._files
currentNode.addFiles(node.getFiles())
return
}
// if the package of node "comes before" that of currentNode, move to the left
if (currentNode.getPackageName() > node.getPackageName()) {
// if the left child of node is defined, that becomes the current node
if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
// else construct it, and we're done
else {
currentNode.setLeftChild(node)
return
}
}
// if the package of node "comes after" that of currentNode, move to the right
if (currentNode.getPackageName() < node.getPackageName()) {
// if the right child of node is defined, that becomes the current node
if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
// else construct it, and we're done
else {
currentNode.setRightChild(node)
return
}
}
throw Error("Whoa, some infinite looping was about to happen!")
}
}
}
}
UtilityFunctions.gs
/**
* Sorts file objects by their name property, alphabetically
* #param { { name : string } } lvalue
* #param { { name : string } } rvalue
* #returns { boolean } the lexical comparison of lvalue.name,rvalue.name
**/
function fileSorter(lvalue, rvalue) {
if (lvalue.name > rvalue.name) return 1;
return (lvalue.name < rvalue.name) ? -1 : 0;
}
Problem
I'm unit-testing the code, with the failing test case consisting of the following steps :
construct a SheetDataNode node
construct another SheetDataNode otherNode with the same package name as the first, but different filename
insert otherNode into node
expectation: it now has two files
actual: it only has one: the original.
expectation: neither left nor right child nodes were set by this operation
actual : neither left nor right child nodes were set by this operation
The code to do the above looks like this:
QUnit.test("inserting a node having the same package as the node it is assigned to",
function() {
// create the base node
var node = SheetDataNode("example", "main.go", 3, 1)
// insert an other node, with identical package name
var otherNode = SheetDataNode(node.getPackageName(), "logUtility.go", 12, 3)
node.insertNode(otherNode)
// node should contain two files, and neither a left child nor a right child
deepEqual(node.getFiles().map(function(val) {
return val.name
}),
["logUtility.go", "main.go"],
"node contains the right file names")
equal(node.getFiles().length, 2, "A package got added to the node")
ok(!node.getLeftChild(), "leftChild still unset")
ok(!node.getRightChild(), "rightChild still unset")
})
Here is screenshot of the failing assertions:
Remember that the method under test is like this:
insertNode : function(node) {
// set the current node as the head node
var currentNode = this;
// while we are on a non-null node
while (currentNode) {
// if the package of node is the same as that of currentNode
if (currentNode.getPackageName() === node.getPackageName()) {
// simply add the files of node to currentNode._files
currentNode.addFiles(node.getFiles())
return
}
// if the package of node "comes before" that of currentNode, move to the left
if (currentNode.getPackageName() > node.getPackageName()) {
// if the left child of node is defined, that becomes the current node
if (currentNode.getLeftChild()) currentNode = currentNode.getLeftChild()
// else construct it, and we're done
else {
currentNode.setLeftChild(node)
return
}
}
// if the package of node "comes after" that of currentNode, move to the right
if (currentNode.getPackageName() < node.getPackageName()) {
// if the right child of node is defined, that becomes the current node
if (currentNode.getRightChild()) currentNode = currentNode.getRightChild()
// else construct it, and we're done
else {
currentNode.setRightChild(node)
return
}
}
throw Error("Whoa, some infinite looping was about to happen!")
}
The test against the method addFiles, which has this code:
QUnit.test("testing method addFiles",
function() {
// create the base node
var node = SheetDataNode("example", "main.go", 3, 1)
// create an array of files to add
const filesToAdd = [{
name : 'aFile.go',
complexity : 10
}, {
name : 'anotherFile.go',
complexity : 10
}, {
name : 'yetAnotherFile.go',
complexity : 10
}]
// is node.getFiles() an array?!
ok(Array.isArray(node.getFiles()), "node.getFiles() is an array")
// add the files
node.addFiles(filesToAdd)
Logger.log(node.getFiles())
// node.getFiles() should be an Array
ok(Array.isArray(node.getFiles()), "node.getFiles() is still an array")
// node.getFiles should now contain filesToAdd
equal(node.getFiles().length, 1 + filesToAdd.length, "node.getFiles().length increased by the length of the files to add")
})
passes:
, as do the other tests against insertNode, meaning the problem might exist with how we try to reference currentNode in insertNode for array property modification. If so, I have no idea how else to reference, in Google Apps Script, the SheetDataNode to undergo state change

I was able to solve the problem, with inspiration from the MDN docs on closures, by changing the private function property declaration from :
_addFile = function(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
to
function _addFile(file) {
for (var f in _files) {
if (file.name < _files[f].name) {
_files.splice(f, 0, file)
return
}
}
_files.push(file)
}
idk why this works, because I forgot the difference between declaring method like a function variable (what I was doing), and preceding the name of the method with function like it's any other function. I'll have to (re-)learn that...

Related

How to remove a specific object from a list in dart/flutter?

I have a list called selected list of type dynamic and it holds a list of objects, each objects contains from a teacher ID & index.
I want to check if this list contains the same Id & index, if it does i want to remove this object from the list.
here is my code ....
void addTeacher(int teacherId, int index) {
if (this.selectedList.contains({ **/// the problem is here** })) {
this.selectedList.remove({teacherId, index});
this.myColor = Colors.grey;
print('removed teacher => ${teacherId.toString()}');
} else {
this.selectedList.add({teacherId, index});
this.myColor = AsasColors().blue;
print('added teacher => ${teacherId.toString()}');
}
notifyListeners();
print(selectedList);
}
how can i achive this ?
Contains and remove use the == operator which in this case will return false because unless you override it for a specific class it will compare by reference.
You can use indexWhere to find out if an item is in a list based on a compare function like that (if the function returns -1 the item is not on the list:
// Index different than -1 means the item is found somewhere in the list
final teacherIndex = this.selectedList.indexWhere((teacher) => teacher['teacherId'] == teacherId);
if (teacherIndex != -1) {
this.selectedList.removeAt(teacherIndex);
this.myColor = Colors.grey;
print('removed teacher => ${teacherId.toString()}');
} else {
...
}
I have implemented it and it worked fine
[The code]
[The output:]
This is the written code:
class Subject {
int? teacherID;
int? subjectID;
Subject(this.teacherID, this.subjectID);
#override
String toString() => "Subject {teacherID: $teacherID, subjectID: $subjectID";
//TODO: Change your needed criteria here..
#override
bool operator ==(Object other) =>
other is Subject &&
teacherID == other.teacherID &&
subjectID == other.subjectID;
}
void addSubject(List<Subject> list, Subject subject) {
if (list.contains(subject)) {
list.remove(subject);
} else {
list.add(subject);
}
}
void main() {
List<Subject> selectedList =
List.generate(10, (index) => Subject(index + 1, index + 1));
print("SelectedList = $selectedList");
addSubject(selectedList, Subject(11, 11));
addSubject(selectedList, Subject(11, 12));
addSubject(selectedList, Subject(12, 11));
addSubject(selectedList, Subject(12, 12));
print("SelectedList2 = $selectedList");
addSubject(selectedList, Subject(12, 12));
print("SelectedList3 = $selectedList");
}
Sincerely, accept the answer if it worked for you.

Initializing Lists to Avoid Potential Errors

This function
List<int> _calculateTrips() {
List<int> trips = [];
trips = List.generate(
30,
(index) {
var counter = 0;
var aDay = DateTime.now().subtract(Duration(days: index));
for (var aWalk in walks) {
if ((aDay.month == aWalk.month) && (aDay.day == aWalk.day)) {
counter++;
}
}
trips.add(counter);
},
);
return trips;
}
creates the error The body might complete normally, causing null to be returned, but the return type is a potentially non-nullable type.Try adding either a return or a throw statement at the end. I'm struggling a bit to understand the message because (a) I thought I initialized the list at the beginning of the function and (b) I thought I had a return statement at the end.
The issue is with the function you pass to List.generate(). It expects a E Function(int), where E is the type of the element, for example:
final evenNumbers = List.generate(10, (index) {
return index * 2;
});
Your issue comes from trips.add(counter):
List<int> trips = [];
trips = List.generate(30, (index) {
final trip = calculateTrip(index);
trips.add(trip);
})
The inner function needs to be an int Function(int) (i.e. a function that takes an int, and returns an int), because your list is a List<int>.
However, your inner function never returns anything.
Simply replace trips.add(counter); with return counter; and it should solve this error. You may also want to refactor your function a little:
List<int> _calculateTrips() => List.generate(30, (index {
var counter = 0;
var aDay = DateTime.now().subtract(Duration(days: index));
for (var aWalk in walks) {
if ((aDay.month == aWalk.month) && (aDay.day == aWalk.day)) {
counter++;
}
}
return counter;
});

How to update a variable maintaining the length of a list in a cascade-like structure?

class CoinData {
var _controller = StreamController<Map<String,dynamic>>();
.....
Stream<Map<String,dynamic>> getAllCurrentRates() {
int numAssets = 0;
int counter = 0;
this.listAllAssets()
.then((list) {
if (list != null) {
numAssets = list.length;
List<Map<String, dynamic>>.from(list)
.where((map) => map["type_is_crypto"] == 1)
.take(3)
.map((e) => e["asset_id"].toString())
.forEach((bitCoin) {
this.getCurrentRate(bitCoin)
.then((rate) => _controller.sink.add(Map<String,dynamic>.from(rate)))
.whenComplete(() {
if (++counter >= numAssets) _controller.close();
});
});
}
});
return _controller.stream;
}
.....
}
The length of returned list is around 2500 and this value is assumed by numAssets, however as you see that list is modified later and therefore its length is less, then the evaluation (++counter >= numAssets) is incorrect. So, is it possible to fix that code maintaining its current structure?
.take(3) is temporal, it shall be removed later.

How to sort List<File> in Dart with null objects at end

Starting to take in hand Flutter for a study project, I'm wondering about sorting a list of files.
Indeed, my program has a list of 4 files initialized like this :
List<File> imageFiles = List(4);
This initialization actually implies that my list is like this : [null,null,null,null].
When the user performs actions, this list can fill up. However, the user can delete a file at any time, which can give us the following situation: [file A, null, null, file d].
My question is, how to sort the list when a deletion arrives in order to have a list where null objects are always last ([file A, file D, null, null]).
I've looked at a lot of topics already, but they never concern the DART.
Thank you in advance for your help.
You can sort the list with list.sort((a, b) => a == null ? 1 : 0);
Here's a full example, with String instead of File, that you can run on DartPad
void main() {
List<String> list = List(4);
list[0] = "file1";
list[3] = "file4";
print("list before sort: $list");
// list before sort: [file1, null, null, file4]
list.sort((a, b) => a == null ? 1 : 0);
print("list after sort: $list");
// list after sort: [file1, file4, null, null]
}
If it's a business requirement to have a max of 4 files, I would suggest creating a value object that can handle with that.
For example:
class ImageList {
final _images = List<String>();
void add(String image) {
if(_images.length < 4) {
_images.add(image);
}
}
void removeAt(int index) {
_images.removeAt(index);
}
String get(int index) {
return _images[index];
}
List getAll() {
return _images;
}
}
And you could run it like this:
void main() {
ImageList imageList = ImageList();
imageList.add("file1");
imageList.add("file2");
imageList.add("file3");
imageList.add("file4");
imageList.add("file5"); // won't be add
print("imagelist: ${imageList.getAll()}");
// imagelist: [file1, file2, file3, file4]
imageList.removeAt(2); // remove file3
print("imagelist: ${imageList.getAll()}");
// imagelist: [file1, file2, file4]
}
This will make it easier to have control. (This example was again with String instead of File)
You can try this:
This place all null at end.
sortedList.sort((a, b) {
int result;
if (a == null) {
result = 1;
} else if (b == null) {
result = -1;
} else {
// Ascending Order
result = a.compareTo(b);
}
return result;
})

gmock gtest how to setup the mock

Please consider the below code sample
NodeInterface * pPreNode = NULL;
NodeInterface * pChild = NULL;
for (uint16_t Index = 0; Index < Children.size(); ++Index)
{
pChild = Children[Index];
if (pPreNode == NULL)
{
pChild->SetPrevious(pChild);
pChild->SetNext(pChild);
}
else
{
pChild->SetNext(pPreNode->GetNext());
pChild->SetPrevious(pPreNode);
pPreNode->GetNext()->SetPrevious(pChild);
pPreNode->SetNext(pChild);
}
pPreNode = pChild;
}
To test this lines how to setup the mock exactly?
Children is a vector of Nodes and we are passing Mocked objects.
EXPECT_CALL(Obj, GetNode()).WillOnce(Invoke(this, &GetANewNode));
and the GetANewNode will provide new MockedNode
MockedNode * GetANewNode()
{
MockedNode * pMockedNode = new MockedNode();
return pMockedNode;
}
How to provide exact nodes for each Next(), Previous() calls?
EXPECT_CALL(*pMockedNode, SetNext(_));
EXPECT_CALL(*pMockedNode, SetPrevious(_));
EXPECT_CALL(*pMockedNode, GetNext());
EXPECT_CALL(*pMockedNode, GetPrevious());
Simple solution is to have all mocked nodes predefined before test case. And use Sequence/InSequence to be sure that everything happens in proper order.
class ObjTest : public ::testing::Test
{
protected:
const std::size_t N = ...; // I do not know how many do you need
std::vector<MockedNode> mockedNode;
std::vector<Node*> children;
Sequence s;
.... Obj; // I am not sure what is Obj in your question
ObjTest () : mockedNode(N)
{}
void SetUp() override
{
// initial setup
EXPECT_CALL(Obj, GetNode()).WillOnce(Return(&mockedNode.front())).InSequence(s);
}
};
Having such test class with initial setup - you can create test cases testing various scenarios that happen after initial sequence:
TEST_F(ObjTest, shouldLinkOneNodeToItself)
{
std::vector<Node*> children { &mockedNode[0] };
EXPECT_CALL(mockedNode[0], SetNext(&mockedNode[0])).InSequence(s);
EXPECT_CALL(mockedNode[0], SetPrevious(&mockedNode[0])).InSequence(s);
Obj.testedFunction(children); // you have not provided your tested function name...
}
And very similar test case for two children:
TEST_F(ObjTest, shouldLinkTwoNodesToEachOther)
{
std::vector<Node*> children { &mockedNode[0], &&mockedNode[1] };
// first interation
EXPECT_CALL(mockedNode[0], SetNext(&mockedNode[0])).InSequence(s);
EXPECT_CALL(mockedNode[0], SetPrevious(&mockedNode[0])).InSequence(s);
// second iteration
EXPECT_CALL(mockedNode[0], GetNext()).WillOnce(Return(&mockedNode[0])).InSequence(s);
EXPECT_CALL(mockedNode[1], SetNext(&mockedNode[0])).InSequence(s);
EXPECT_CALL(mockedNode[1], SetPrevious(&mockedNode[0])).InSequence(s);
// etc...
Obj.testedFunction(children);
}