gpii-universal
Version:
Cross platform, core components of the GPII personalization infrastructure.
434 lines (354 loc) • 15.4 kB
JavaScript
/*
* iniFile Tests
*
* Copyright 2018 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The R&D leading to these results received funding from the
* Department of Education - Grant H421A150005 (GPII-APCP). However,
* these results do not necessarily represent the policy of the
* Department of Education, and you should not assume endorsement by the
* Federal Government.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
;
var fluid = require("infusion");
var fs = require("fs"),
JSON5 = require("json5");
var jqUnit = fluid.require("node-jqunit");
var gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.tests.iniFile");
require("../index.js");
var teardowns = [];
jqUnit.module("gpii.tests.iniFile", {
teardown: function () {
while (teardowns.length) {
teardowns.pop()();
}
}
});
gpii.tests.iniFile.parserTestData = fluid.freezeRecursive({
input: [
"key1=value1",
"key2=value2",
"not-a-key", // ignored
"[section1]",
"s1key1=value-s1key1",
"s1key2=value-s1key2",
"# comment", // ignored
"[section2]",
"s2key1=value-s2key1",
"s2key2=\"value-s2key2\"",
"[[section2A]]",
"s2Akey1=\"\"\"value-s2Akey1",
"line2-s2Akey1\"\"\"",
"s2Akey2=value-s2Akey2",
"[[section2B]]",
"s2Bkey1=value-s2Bkey1",
" line2-s2Bkey1",
"s2Bkey2=value-s2Bkey2"
],
expect: [
// implied
["sectionBegin", "state", []],
// key1=value1
["gotValue", "state", [], "key1", "value1", false],
// key2=value2
["gotValue", "state", [], "key2", "value2", false],
// [section1]
["sectionBegin", "state", ["section1"]],
// s1key1=value-s1key1
["gotValue", "state", ["section1"], "s1key1", "value-s1key1", false],
// s1key2=value-s1key2
["gotValue", "state", ["section1"], "s1key2", "value-s1key2", false],
// # comment
// [section2]
["sectionEnd", "state", ["section1"], false],
["sectionBegin", "state", ["section2"]],
// s2key1=value-s2key1
["gotValue", "state", ["section2"], "s2key1", "value-s2key1", false],
// s2key2="value-s2key2"
["gotValue", "state", ["section2"], "s2key2", "value-s2key2", true],
// [[section2A]]
["sectionBegin", "state", ["section2", "section2A"]],
// s2Akey1="""value-s2Akey1
// line2-s2Akey1"""
["gotValue", "state", ["section2", "section2A"], "s2Akey1", "value-s2Akey1\nline2-s2Akey1", true],
// s2Akey2=value-s2Akey2
["gotValue", "state", ["section2", "section2A"], "s2Akey2", "value-s2Akey2", false],
// [[section2B]]
["sectionEnd", "state", ["section2", "section2A"], false],
["sectionBegin", "state", ["section2", "section2B"]],
// s2Bkey1=value-s2Bkey1
// line2-s2Bkey1
["gotValue", "state", ["section2", "section2B"], "s2Bkey1", "value-s2Bkey1\nline2-s2Bkey1", false],
// s2Bkey2=value-s2Bkey
["gotValue", "state", ["section2", "section2B"], "s2Bkey2", "value-s2Bkey2", false],
// implied from eof
["sectionEnd", "state", ["section2", "section2B"], true],
["sectionEnd", "state", ["section2"], true],
["sectionEnd", "state", [], true]
]
});
/**
* Shows the deep differences between two objects, in the console.
*
* @param {Object} expected What's expected.
* @param {Object} actual What really happened.
* @return {Boolean} true if both objects are the same.
*/
gpii.tests.iniFile.showDiff = function (expected, actual) {
var diff = {changeMap: {}, changes: 0, unchanged: 0};
var same = fluid.model.diff(actual, expected, diff);
if (!same) {
var inspect = function (changeMap, path) {
return fluid.transform(changeMap, function (value, key) {
if (fluid.isPlainObject(value)) {
return inspect(value, path + (path && ".") + key);
} else {
// fluid.get can't support 0 length key names.
var exp = fluid.get(expected, path);
var act = fluid.get(actual, path);
return {
expected: exp && exp[key] || undefined,
actual: act && act[key] || undefined
};
}
});
};
var output = inspect(diff.changeMap, "");
fluid.log(diff.changeMap);
fluid.log("Differences between actual and expected:", output);
}
return same;
};
/**
* Extracts expected data from an INI file. The expected data is a json object made from the lines starting with "#@".
*
* @param {String} iniFile The ini file to load.
* @return {Object} The data object, and INI file content (without the #@ lines).
*/
gpii.tests.iniFile.loadTestData = function (iniFile) {
var orig = fs.readFileSync(iniFile, "utf8");
// Extract the expected data. The expect json string is a combination of lines beginning with #@, and the
// ini content is the other lines.
var expectedLines = ["{"];
var iniContent = orig.replace(/^#@([^\n]*?([,{:])?)\n|^.*$/gm, function (all, expect, end) {
var result;
if (expect) {
// A "#@" line. Add it to the expected lines, and remove from the result.
if (!end) {
expect += ",";
}
expectedLines.push(expect);
result = "";
} else {
// A content line; Keep it in the result.
expectedLines.push("// " + all);
result = all;
}
return result;
});
expectedLines.push("}");
var expectedJson = expectedLines.join("\n");
// Parse the extracted json.
var data;
try {
data = JSON5.parse(expectedJson);
} catch (e) {
var output;
if (e.lineNumber) {
// Provide a clue where the error is.
output = expectedLines.slice(Math.max(e.lineNumber - 10, 0), e.lineNumber).join("\n");
} else {
output = expectedLines;
}
// Need to change the log length to provide anything meaningful.
var oldChars = fluid.logObjectRenderChars;
fluid.logObjectRenderChars = 0xfffff;
fluid.log("expectedJson:\n", output);
fluid.logObjectRenderChars = oldChars;
jqUnit.fail(e);
iniContent = data = null;
}
return fluid.freezeRecursive({
content: iniContent,
data: data
});
};
// Tests the parser calls the correct callbacks with the correct values.
jqUnit.test("inifile parser", function () {
var testData = gpii.tests.iniFile.parserTestData;
jqUnit.expect(testData.expect.length * 2);
var expectedIndex = 0;
var gotCallback = function (name) {
return function () {
var expect = testData.expect[expectedIndex++].slice();
var expectedName = expect.shift();
jqUnit.assertEquals("The expected handler should be called.", expectedName, name);
var args = fluid.makeArray(arguments);
jqUnit.assertDeepEq("The handler should be called with expected parameters.", expect, args);
};
};
var iniContent = testData.input.join("\n");
gpii.iniFile.parse(iniContent, {
sectionBegin: gotCallback("sectionBegin"),
sectionEnd: gotCallback("sectionEnd"),
gotValue: gotCallback("gotValue")
}, {state: "state"});
});
// Tests reading an ini file, with gpii.iniFile.read().
jqUnit.test("inifile reading", function () {
var testData = gpii.tests.iniFile.loadTestData(__dirname + "/read-test.ini");
jqUnit.assertTrue("Test data should load", testData && testData.data && testData.content);
// Parse the ini file.
var iniData = gpii.iniFile.read(testData.content);
jqUnit.assertTrue("gpii.iniFile.read should return an object", fluid.isPlainObject(iniData));
// And again, checks there isn't any state left over.
var iniDataAgain = gpii.iniFile.read(testData.content);
jqUnit.assertDeepEq("gpii.iniFile.read should be repeatable", iniData, iniDataAgain);
// Compare it to the expected data.
var same = gpii.tests.iniFile.showDiff(testData.data, iniData);
// Both asserts should be the same
jqUnit.assertDeepEq("ini data should match the expected (assertDeepEq)", iniData, testData.data);
jqUnit.assertTrue("ini data should match the expected (showDiff)", same);
});
// Test readFile() returns the same result as read().
jqUnit.test("readFile", function () {
// Parse the file.
var filename = __dirname + "/read-test.ini";
var readFileData = gpii.iniFile.readFile(filename);
// Parse the content.
var content = fs.readFileSync(filename, "utf8");
var readData = gpii.iniFile.read(content);
jqUnit.assertDeepEq("readFile and read should return the same", readFileData, readData);
// Make sure it didn't touch anything.
var contentAfter = fs.readFileSync(filename, "utf8");
jqUnit.assertEquals("readFile should not modify the file.", content, contentAfter);
});
// Test writing ini data, with gpii.iniFile.write().
jqUnit.test("inifile writing", function () {
var testIniPath = __dirname + "/write-test.ini";
var expectedIniPath = __dirname + "/write-test.expect.ini";
var content = fs.readFileSync(testIniPath, "utf8");
var iniData = gpii.iniFile.read(content);
var writtenContent = gpii.iniFile.write(content, iniData);
// No changes made to the data, so nothing should have changed in the content.
jqUnit.assertEquals("INI content should match original after no change", content, writtenContent);
var newIniData = fluid.copy(iniData);
// Change all the values, removing those whose name begin with remove_.
var changeValues = function (obj) {
fluid.each(obj, function (value, key) {
if (key.startsWith("remove_")) {
delete obj[key];
} else if (fluid.isPlainObject(value)) {
changeValues(value);
} else {
var newValue = "abc " + key + " xyz";
if (key.startsWith("ml_")) {
var num = parseInt(key[3]) || 3;
var lines = [];
for (var l = 0; l < num; l++) {
lines.push(newValue + " line" + l);
}
newValue = lines.join("\n");
}
obj[key] = newValue;
}
});
};
changeValues(newIniData);
// Make some specific changes
newIniData.empty_me = "";
newIniData.null_me = null;
newIniData.number_me1 = 42;
newIniData.number_me2 = 12345;
newIniData.true_me1 = true;
newIniData.true_me2 = true;
// Add some new ones
newIniData.new_empty = "";
newIniData.new_null = null;
newIniData.new_number = 42;
newIniData.new_true = true;
newIniData.newKey1 = "new value";
newIniData.section1.newKey1 = "new value 1, in section1";
newIniData.section1.newKey2 = "new value 2";
newIniData.newSection1 = {
newKey1: "new value 1, in new section 1",
newKey3: "new value 3"
};
newIniData.newSection2 = {
newKey1: "new value 1, in new section 2",
newKey4: "new value 4",
newSubSection: {
newKey1: "new value 1, in new sub section",
newKey5: "new value 5"
}
};
// Write the ini data.
writtenContent = gpii.iniFile.write(content, newIniData);
// Read the expected ini file.
var expectedContent = fs.readFileSync(expectedIniPath, "utf8");
// Compare the textual result.
jqUnit.assertEquals("The written content should contain the same text as the expected ini file.",
expectedContent, writtenContent);
// See if it matches the parsed expected ini (it really should, considering the content is identical).
var writtenData = gpii.iniFile.read(writtenContent);
var expectedData = gpii.iniFile.read(expectedContent);
var same = gpii.tests.iniFile.showDiff(expectedData, writtenData);
jqUnit.assertTrue("Written data should match the expected data.", same);
jqUnit.assertDeepEq("Written data should match the expected data (assertDeepEq).",
expectedData, writtenData);
// Write the original data back again.
var writtenOriginalContent = gpii.iniFile.write(writtenContent, iniData);
// Even though it contains the same data, the text will be different because things have been removed and re-added,
// so just compare the parsed data.
var writtenOriginalData = gpii.iniFile.read(writtenOriginalContent);
same = gpii.tests.iniFile.showDiff(iniData, writtenOriginalData);
jqUnit.assertTrue("Re-written original data should match the original data.", same);
jqUnit.assertDeepEq("Re-written original data should match the original data (assertDeepEq).",
iniData, writtenOriginalData);
});
// Tests writing data to empty content.
jqUnit.test("inifile writing (empty file)", function () {
var filename = __dirname + "/write-test.ini";
var iniData = gpii.iniFile.readFile(filename);
var writtenContent = gpii.iniFile.write("", iniData);
// Check if the written content parses to the same data as the input.
var writtenData = gpii.iniFile.read(writtenContent);
var same = gpii.tests.iniFile.showDiff(iniData, writtenData);
jqUnit.assertTrue("Written data should match the parsed expected file.", same);
jqUnit.assertDeepEq("Written data should match the parsed expected file (assertDeepEq).",
iniData, writtenData);
});
// Tests writing data to new file.
jqUnit.test("inifile reading/writing non-existent file", function () {
var filename = __dirname + "/does-not-exist" + Math.random();
var readContent = gpii.iniFile.readFile(filename, { key: "value" });
jqUnit.assertDeepEq("Non-existent file should be read as though it's empty", {}, readContent);
var writtenContent = gpii.iniFile.writeFromFile(filename, { key: "value" });
jqUnit.assertEquals("INI content should be as expected", "key=value", writtenContent.trim());
// Check writing an empty object creates an empty file.
var writtenContent2 = gpii.iniFile.writeFromFile(filename, { });
jqUnit.assertEquals("Empty INI content should be as expected", "", writtenContent2.trim());
});
jqUnit.test("iniFile add values to last section", function () {
var content = "[section]\nkey1=a\nkey2=b";
var dataExpect = {
section: {
key1: "a",
key2: "b"
}
};
var data = gpii.iniFile.read(content);
jqUnit.assertDeepEq("Content without trailing newline should parse correctly", dataExpect, data);
data.section.key3 = "abc";
data.section.key2 = "123";
var writtenContent = gpii.iniFile.write(content, data);
var expectedContent = "[section]\nkey1=a\nkey2=123\nkey3=abc";
jqUnit.assertDeepEq("Last item should be changed", expectedContent, writtenContent);
});