diff --git a/awx/ui/static/js/system-tracking/compare-facts/nested-helpers.js b/awx/ui/static/js/system-tracking/compare-facts/nested-helpers.js index b55962639f..3a8fea8926 100644 --- a/awx/ui/static/js/system-tracking/compare-facts/nested-helpers.js +++ b/awx/ui/static/js/system-tracking/compare-facts/nested-helpers.js @@ -130,12 +130,33 @@ export function findFacts(factData) { var leftData = factData[0].facts; var rightData = factData[1].facts; + // For value types (boolean, number) or complex objects use the value + // as is. For collection types, return 'absent' if it's empty + // otherwise, use the value as is. + // + function factValueOrAbsent(value) { + + if (_.isBoolean(value) || + _.isNumber(value)) { + return value; + } + + if (_.isNull(value) || _.isEmpty(value)) { + return 'absent'; + } + + return value; + } + function factObject(keyPath, key, leftValue, rightValue) { var obj = { keyPath: keyPath, keyName: key }; + leftValue = factValueOrAbsent(leftValue); + rightValue = factValueOrAbsent(rightValue); + if (factData[0].position === 'left') { obj.value1 = leftValue; obj.value2 = rightValue; @@ -155,10 +176,10 @@ export function findFacts(factData) { }, []); } else { + // default value in _.get ('absent') only hits if it's undefined, so we need to handle other falsey cases with || var rightValue = _.get(rightData, - parentKeys, - 'absent'); + parentKeys); return factObject( // TODO: Currently parentKeys is getting passed with the final key diff --git a/awx/ui/static/js/system-tracking/compare-facts/nested.js b/awx/ui/static/js/system-tracking/compare-facts/nested.js index a3943cad7c..31e98526a7 100644 --- a/awx/ui/static/js/system-tracking/compare-facts/nested.js +++ b/awx/ui/static/js/system-tracking/compare-facts/nested.js @@ -8,6 +8,12 @@ import {formatFacts, findFacts} from './nested-helpers'; export default function nestedCompare(basisFacts, comparatorFacts) { + if (_.isEmpty(basisFacts.facts)) { + var tmp = _.merge({}, basisFacts); + basisFacts = _.merge({}, comparatorFacts); + comparatorFacts = tmp; + } + var factsList = [basisFacts, comparatorFacts]; factsList = findFacts(factsList); factsList = compareFacts(factsList); diff --git a/awx/ui/tests/unit/system-tracking/compare-facts/nested-test.js b/awx/ui/tests/unit/system-tracking/compare-facts/nested-test.js new file mode 100644 index 0000000000..4014650901 --- /dev/null +++ b/awx/ui/tests/unit/system-tracking/compare-facts/nested-test.js @@ -0,0 +1,179 @@ +import compareFacts from 'tower/system-tracking/compare-facts/nested'; + +/* jshint node: true */ +/* globals -expect, -_ */ + +var _, expect; + +// This makes this test runnable in node OR karma. The sheer +// number of times I had to run this test made the karma +// workflow just too dang slow for me. Maybe this can +// be a pattern going forward? Not sure... +// +(function(global) { + var chai = global.chai || require('chai'); + + if (typeof window === 'undefined') { + var chaiThings = global.chaiThings || require('chai-things'); + chai.use(chaiThings); + } + + _ = global._ || require('lodash'); + expect = global.expect || chai.expect; + + global.expect = expect; + + + + global._ = _; + +})(typeof window === 'undefined' ? global : window); + + +describe('CompareFacts.Nested', function() { + + it('returns empty array with no fact data', function() { + var result = compareFacts({ facts: [] }, { facts: [] }); + + expect(result).to.deep.equal([]); + }); + + it('returns empty array when no differences', function() { + var result = compareFacts( + { facts: + { 'testing_facts': + { 'foo': 'bar' + } + } + }, + { facts: + { 'testing_facts': + { 'foo': 'bar' + } + } + }); + + expect(result).to.deep.equal([]); + }); + + context('when only left set has data', function() { + + it('returns values with data for value1 and "absent" for value2', function() { + + var result = compareFacts( + { position: 'left', + facts: + { 'testing_facts': + { 'foo': 'bar' + } + } + }, + { position: 'right', + facts: + {} + }); + + expect(result[0].facts).to.contain + .an.item + .with.property('value1', 'bar'); + + expect(result[0].facts).to.contain + .an.item + .with.property('value2', 'absent'); + + }); + + }); + + context('when only right set has data', function() { + + it('returns values with data for value2 and "absent" for value1', function() { + + var result = compareFacts( + { position: 'left', + facts: + {} + }, + { position: 'right', + facts: + { 'testing_facts': + { 'foo': 'bar' + } + } + }); + + expect(result).not.to.be.empty; + + expect(result[0].facts).to.contain + .an.item + .with.property('value1', 'absent'); + + expect(result[0].facts).to.contain + .an.item + .with.property('value2', 'bar'); + + }); + + }); + + context('when both sets have fact data and differences exist', function() { + + it('does not consider false values "absent"', function() { + var result = compareFacts( + { position: 'left', + facts: + { 'testing_facts': + { 'foo': false + } + } + }, + { position: 'right', + facts: + { 'testing_facts': + { 'foo': true + } + } + }); + + expect(result[0].facts).to.contain + .an.item + .with.property('value1', false); + + expect(result[0].facts).to.contain + .an.item + .with.property('value2', true); + }); + + it('uses "absent" for both values when neither has data', function() { + var result = compareFacts( + { position: 'left', + facts: + { 'testing_facts': + { 'foo': 'baz', + 'blah': null + } + } + }, + { position: 'right', + facts: + { 'testing_facts': + { 'foo': 'bar', + 'blah': null + } + } + }); + + expect(result[0].facts).to.contain + .an.item + .with.property('value1', 'absent'); + + expect(result[0].facts).to.contain + .an.item + .with.property('value2', 'absent'); + + + + }); + }); + +});