UNPKG

gpii-universal

Version:

Cross platform, core components of the GPII personalization infrastructure.

434 lines (354 loc) 15.4 kB
/* * 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 */ "use strict"; 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); });