From 9ea037334e27d2923bbf9040681cb97cf31906e4 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Wed, 8 Nov 2023 20:26:39 +0100 Subject: [PATCH 01/16] starting SymptomVsSymptomChartView --- docs/SymptomsCausedByCOVIDLots/index.html | 22 +++- .../js/PageInitializer.js | 6 +- .../js/SymptomVsSymptomChartView.js | 115 ++++++++++++++++++ 3 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js diff --git a/docs/SymptomsCausedByCOVIDLots/index.html b/docs/SymptomsCausedByCOVIDLots/index.html index 3311eb5b0c6..7f52c3667c0 100644 --- a/docs/SymptomsCausedByCOVIDLots/index.html +++ b/docs/SymptomsCausedByCOVIDLots/index.html @@ -9,14 +9,14 @@ Safety Signals for COVID Batches - + @@ -35,6 +35,7 @@ + + @@ -36,6 +37,7 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/docs/SymptomsCausedByCOVIDLots/test/jshamcrest.js b/docs/SymptomsCausedByCOVIDLots/test/jshamcrest.js new file mode 100644 index 00000000000..ab7d89944a0 --- /dev/null +++ b/docs/SymptomsCausedByCOVIDLots/test/jshamcrest.js @@ -0,0 +1,1500 @@ +/* + * JsHamcrest v0.7.0 + * http://danielfm.github.com/jshamcrest/ + * + * Library of matcher objects for JavaScript. + * + * Copyright (c) 2009-2013 Daniel Fernandes Martins + * Licensed under the BSD license. + * + * Revision: 13d2f6ac0272b6b679eca295514fe69c0a84251a + * Date: Sat Jan 26 12:47:27 2013 -0200 + */ + +var JsHamcrest = { + /** + * Library version. + */ + version: '0.7.0', + + /** + * Delegate function, useful when used to create a matcher that has a value-equalTo semantic + */ + EqualTo: function (func) { + return function (matcherOrValue) { + if (!JsHamcrest.isMatcher(matcherOrValue)) { + return func(JsHamcrest.Matchers.equalTo(matcherOrValue)); + } + return func(matcherOrValue); + }; + }, + + /** + * Returns whether the given object is a matcher. + */ + isMatcher: function(obj) { + return obj instanceof JsHamcrest.SimpleMatcher; + }, + + /** + * Returns whether the given arrays are equivalent. + */ + areArraysEqual: function(array, anotherArray) { + if (array instanceof Array || anotherArray instanceof Array) { + if (array.length != anotherArray.length) { + return false; + } + + for (var i = 0; i < array.length; i++) { + var a = array[i]; + var b = anotherArray[i]; + + if (a instanceof Array || b instanceof Array) { + if(!JsHamcrest.areArraysEqual(a, b)) { + return false; + } + } else if (a != b) { + return false; + } + } + return true; + } else { + return array == anotherArray; + } + }, + + /** + * Returns whether the given Arrays are equivalent. This will return true if the objects + * inside the Arrays are equivalent i.e. they dont have to be the same object reference. + * Two objects with the same key value pairs will be equivalent eventhough they are not + * the same object. + * + * @param {type} expected A map of expected values. + * @param {type} actual A map of the actual values. + * @returns {Boolean} A Boolean signifing if the two Arrays are equivalent, true if they are. + */ + areArraysEquivalent: function(expected, actual) + { + if (expected.length !== actual.length) + { + return false; + } + + for (var i = 0; i < expected.length; i++) + { + var a = expected[i]; + var b = actual[i]; + + if(JsHamcrest.areTwoEntitiesEquivalent(a, b) === false) + { + return false; + } + } + + return true; + }, + + /** + * Returns whether the given maps are equivalent. This will return true if the objects + * inside the maps are equivalent i.e. they dont have to be the same object reference. + * Two objects with the same key value pairs will be equivalent eventhough they are not + * the same object. + * + * @param {type} expected A map of expected values. + * @param {type} actual A map of the actual values. + * @returns {Boolean} A Boolean signifing if the two maps are equivalent, true if they are. + */ + areMapsEquivalent: function(expected, actual) + { + // we need to do this both ways in case both maps have undefined values (which makes counting the number + // of keys a non-acceptable comparison). + if(JsHamcrest.simpleMapCompare(expected, actual) && JsHamcrest.simpleMapCompare(actual, expected)) + { + return true; + } + + return false; + }, + + simpleMapCompare: function(firstMap, secondMap) + { + for(var item in firstMap) + { + if(firstMap.hasOwnProperty(item)) + { + if(!JsHamcrest.areTwoEntitiesEquivalent(firstMap[item], secondMap[item])) return false; + } + } + + return true; + }, + + areTwoEntitiesEquivalent: function(expected, actual) + { + var expectedsMatcher = JsHamcrest.retreiveEntityMatcherFunction(expected); + var actualsMatcher = JsHamcrest.retreiveEntityMatcherFunction(actual); + + if(expectedsMatcher === actualsMatcher && expectedsMatcher(expected, actual)) + { + return true; + } + + return false; + }, + + /** + * Returns the function that would be used to compare the entity with an entity of the same type. + * + * @param {type} entity A JavaScript entity, this method will try and figure out what type. + */ + retreiveEntityMatcherFunction: function(entity) { + if ( (Array.isArray && Array.isArray(entity)) || entity instanceof Array ) return JsHamcrest.areArraysEquivalent; + + if(entity instanceof Boolean) return JsHamcrest.areBooleansEqual; + + if(entity instanceof Date) return JsHamcrest.areDatesEqual; + + if(entity instanceof Function) return JsHamcrest.areFunctionsEqual; + + if(entity instanceof Number || typeof entity === "number") return JsHamcrest.areNumbersEqual; + + if(entity instanceof String || typeof entity === "string") return JsHamcrest.areStringsEqual; + + if(entity instanceof RegExp) return JsHamcrest.areRegExpEqual; + + if(entity instanceof Error) return JsHamcrest.areErrorsEqual; + + if(typeof entity === "undefined") return JsHamcrest.areEntitiesUndefined; + + if(entity === null) return JsHamcrest.areEntitiesNull; + + if(entity.constructor === Object) return JsHamcrest.areMapsEquivalent; + + return JsHamcrest.areEntitiesStrictlyEquals; + }, + + /** + * Simple comparator functions. + * + * @param {type} expected The Object that is expected to be present. + * @param {type} actual The Object that is actually present. + */ + areBooleansEqual: function(expected, actual) { return expected.toString() === actual.toString(); }, + + areDatesEqual: function(expected, actual) { return expected.toString() === actual.toString(); }, + + areFunctionsEqual: function(expected, actual) { return expected === actual; }, + + areNumbersEqual: function(expected, actual) { return expected.valueOf() === actual.valueOf(); }, + + areStringsEqual: function(expected, actual) { return expected.valueOf() === actual.valueOf(); }, + + areRegExpEqual: function(expected, actual) { return expected.toString() === actual.toString(); }, + + areErrorsEqual: function(expected, actual) { return expected.constructor === actual.constructor && expected.message === actual.message; }, + + areEntitiesUndefined: function(expected, actual) { return expected === actual; }, + + areEntitiesNull: function(expected, actual) { return expected === actual; }, + + areEntitiesStrictlyEquals: function(expected, actual) { return expected === actual; }, + + /** + * Builds a matcher object that uses external functions provided by the + * caller in order to define the current matching logic. + */ + SimpleMatcher: function(params) { + params = params || {}; + + this.matches = params.matches; + this.describeTo = params.describeTo; + + // Replace the function to describe the actual value + if (params.describeValueTo) { + this.describeValueTo = params.describeValueTo; + } + }, + + /** + * Matcher that provides an easy way to wrap several matchers into one. + */ + CombinableMatcher: function(params) { + // Call superclass' constructor + JsHamcrest.SimpleMatcher.apply(this, arguments); + + params = params || {}; + + this.and = function(anotherMatcher) { + var all = JsHamcrest.Matchers.allOf(this, anotherMatcher); + return new JsHamcrest.CombinableMatcher({ + matches: all.matches, + + describeTo: function(description) { + description.appendDescriptionOf(all); + } + }); + }; + + this.or = function(anotherMatcher) { + var any = JsHamcrest.Matchers.anyOf(this, anotherMatcher); + return new JsHamcrest.CombinableMatcher({ + matches: any.matches, + + describeTo: function(description) { + description.appendDescriptionOf(any); + } + }); + }; + }, + + /** + * Class that builds assertion error messages. + */ + Description: function() { + var value = ''; + + this.get = function() { + return value; + }; + + this.appendDescriptionOf = function(selfDescribingObject) { + if (selfDescribingObject) { + selfDescribingObject.describeTo(this); + } + return this; + }; + + this.append = function(text) { + if (text != null) { + value += text; + } + return this; + }; + + this.appendLiteral = function(literal) { + var undefined; + if (literal === undefined) { + this.append('undefined'); + } else if (literal === null) { + this.append('null'); + } else if (literal instanceof Array) { + this.appendValueList('[', ', ', ']', literal); + } else if (typeof literal == 'string') { + this.append('"' + literal + '"'); + } else if (literal instanceof Function) { + this.append('Function' + (literal.name ? ' ' + literal.name : '')); + } else { + this.append(literal); + } + return this; + }; + + this.appendValueList = function(start, separator, end, list) { + this.append(start); + for (var i = 0; i < list.length; i++) { + if (i > 0) { + this.append(separator); + } + this.appendLiteral(list[i]); + } + this.append(end); + return this; + }; + + this.appendList = function(start, separator, end, list) { + this.append(start); + for (var i = 0; i < list.length; i++) { + if (i > 0) { + this.append(separator); + } + this.appendDescriptionOf(list[i]); + } + this.append(end); + return this; + }; + } +}; + + +/** + * Describes the actual value to the given description. This method is optional + * and, if it's not present, the actual value will be described as a JavaScript + * literal. + */ +JsHamcrest.SimpleMatcher.prototype.describeValueTo = function(actual, description) { + description.appendLiteral(actual); +}; + + +// CombinableMatcher is a specialization of SimpleMatcher +JsHamcrest.CombinableMatcher.prototype = new JsHamcrest.SimpleMatcher(); +JsHamcrest.CombinableMatcher.prototype.constructor = JsHamcrest.CombinableMatcher; +JsHamcrest.Matchers = {}; + +/** + * The actual value must be any value considered truth by the JavaScript + * engine. + */ +JsHamcrest.Matchers.truth = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual; + }, + + describeTo: function(description) { + description.append('truth'); + } + }); +}; + +/** + * Delegate-only matcher frequently used to improve readability. + */ +JsHamcrest.Matchers.is = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return matcher.matches(actual); + }, + + describeTo: function(description) { + description.append('is ').appendDescriptionOf(matcher); + } + }); +}); + +/** + * The actual value must not match the given matcher or value. + */ +JsHamcrest.Matchers.not = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return !matcher.matches(actual); + }, + + describeTo: function(description) { + description.append('not ').appendDescriptionOf(matcher); + } + }); +}); + +/** + * The actual value must be equal to the given value. + */ +JsHamcrest.Matchers.equalTo = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + if (expected instanceof Array || actual instanceof Array) { + return JsHamcrest.areArraysEqual(expected, actual); + } + return actual == expected; + }, + + describeTo: function(description) { + description.append('equal to ').appendLiteral(expected); + } + }); +}; + +/** + * Useless always-match matcher. + */ +JsHamcrest.Matchers.anything = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return true; + }, + + describeTo: function(description) { + description.append('anything'); + } + }); +}; + +/** + * The actual value must be null (or undefined). + */ +JsHamcrest.Matchers.nil = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual == null; + }, + + describeTo: function(description) { + description.appendLiteral(null); + } + }); +}; + +/** + * The actual value must be the same as the given value. + */ +JsHamcrest.Matchers.sameAs = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual === expected; + }, + + describeTo: function(description) { + description.append('same as ').appendLiteral(expected); + } + }); +}; + +/** + * The actual value is a function and, when invoked, it should thrown an + * exception with the given name. + */ +JsHamcrest.Matchers.raises = function(exceptionName) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actualFunction) { + try { + actualFunction(); + } catch (e) { + if (e.name == exceptionName) { + return true; + } else { + throw e; + } + } + return false; + }, + + describeTo: function(description) { + description.append('raises ').append(exceptionName); + } + }); +}; + +/** + * The actual value is a function and, when invoked, it should raise any + * exception. + */ +JsHamcrest.Matchers.raisesAnything = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actualFunction) { + try { + actualFunction(); + } catch (e) { + return true; + } + return false; + }, + + describeTo: function(description) { + description.append('raises anything'); + } + }); +}; + +/** + * Combinable matcher where the actual value must match both of the given + * matchers. + */ +JsHamcrest.Matchers.both = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.CombinableMatcher({ + matches: matcher.matches, + describeTo: function(description) { + description.append('both ').appendDescriptionOf(matcher); + } + }); +}); + +/** + * Combinable matcher where the actual value must match at least one of the + * given matchers. + */ +JsHamcrest.Matchers.either = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.CombinableMatcher({ + matches: matcher.matches, + describeTo: function(description) { + description.append('either ').appendDescriptionOf(matcher); + } + }); +}); + +/** + * All the given values or matchers should match the actual value to be + * sucessful. This matcher behaves pretty much like the && operator. + */ +JsHamcrest.Matchers.allOf = function() { + var args = arguments; + if (args[0] instanceof Array) { + args = args[0]; + } + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + for (var i = 0; i < args.length; i++) { + var matcher = args[i]; + if (!JsHamcrest.isMatcher(matcher)) { + matcher = JsHamcrest.Matchers.equalTo(matcher); + } + if (!matcher.matches(actual)) { + return false; + } + } + return true; + }, + + describeTo: function(description) { + description.appendList('(', ' and ', ')', args); + } + }); +}; + +/** + * At least one of the given matchers should match the actual value. This + * matcher behaves pretty much like the || (or) operator. + */ +JsHamcrest.Matchers.anyOf = function() { + var args = arguments; + if (args[0] instanceof Array) { + args = args[0]; + } + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + for (var i = 0; i < args.length; i++) { + var matcher = args[i]; + if (!JsHamcrest.isMatcher(matcher)) { + matcher = JsHamcrest.Matchers.equalTo(matcher); + } + if (matcher.matches(actual)) { + return true; + } + } + return false; + }, + + describeTo: function(description) { + description.appendList('(', ' or ', ')', args); + } + }); +}; + +/** + * The actual number must be greater than the expected number. + */ +JsHamcrest.Matchers.greaterThan = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual > expected; + }, + + describeTo: function(description) { + description.append('greater than ').appendLiteral(expected); + } + }); +}; + +/** + * The actual number must be greater than or equal to the expected number + */ +JsHamcrest.Matchers.greaterThanOrEqualTo = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual >= expected; + }, + + describeTo: function(description) { + description.append('greater than or equal to ').appendLiteral(expected); + } + }); +}; + +/** + * The actual number must be less than the expected number. + */ +JsHamcrest.Matchers.lessThan = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual < expected; + }, + + describeTo: function(description) { + description.append('less than ').appendLiteral(expected); + } + }); +}; + +/** + * The actual number must be less than or equal to the expected number. + */ +JsHamcrest.Matchers.lessThanOrEqualTo = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual <= expected; + }, + + describeTo: function(description) { + description.append('less than or equal to ').append(expected); + } + }); +}; + +/** + * The actual value must not be a number. + */ +JsHamcrest.Matchers.notANumber = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return isNaN(actual); + }, + + describeTo: function(description) { + description.append('not a number'); + } + }); +}; + +/** + * The actual value must be divisible by the given number. + */ +JsHamcrest.Matchers.divisibleBy = function(divisor) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual % divisor === 0; + }, + + describeTo: function(description) { + description.append('divisible by ').appendLiteral(divisor); + } + }); +}; + +/** + * The actual value must be even. + */ +JsHamcrest.Matchers.even = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual % 2 === 0; + }, + + describeTo: function(description) { + description.append('even'); + } + }); +}; + +/** + * The actual number must be odd. + */ +JsHamcrest.Matchers.odd = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual % 2 !== 0; + }, + + describeTo: function(description) { + description.append('odd'); + } + }); +}; + +/** + * The actual number must be between the given range (inclusive). + */ +JsHamcrest.Matchers.between = function(start) { + return { + and: function(end) { + var greater = end; + var lesser = start; + + if (start > end) { + greater = start; + lesser = end; + } + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual >= lesser && actual <= greater; + }, + + describeTo: function(description) { + description.append('between ').appendLiteral(lesser) + .append(' and ').appendLiteral(greater); + } + }); + } + }; +}; + +/** + * The actual number must be close enough to *expected*, that is, the actual + * number is equal to a value within some range of acceptable error. + */ +JsHamcrest.Matchers.closeTo = function(expected, delta) { + if (!delta) { + delta = 0; + } + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return (Math.abs(actual - expected) - delta) <= 0; + }, + + describeTo: function(description) { + description.append('number within ') + .appendLiteral(delta).append(' of ').appendLiteral(expected); + } + }); +}; + +/** + * The actual number must be zero. + */ +JsHamcrest.Matchers.zero = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual === 0; + }, + + describeTo: function(description) { + description.append('zero'); + } + }); +}; + +/** + * The actual string must be equal to the given string, ignoring case. + */ +JsHamcrest.Matchers.equalIgnoringCase = function(str) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual.toUpperCase() == str.toUpperCase(); + }, + + describeTo: function(description) { + description.append('equal ignoring case "').append(str).append('"'); + } + }); +}; + +/** + * The actual string must have a substring equals to the given string. + */ +JsHamcrest.Matchers.containsString = function(str) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual.indexOf(str) >= 0; + }, + + describeTo: function(description) { + description.append('contains string "').append(str).append('"'); + } + }); +}; + +/** + * The actual string must start with the given string. + */ +JsHamcrest.Matchers.startsWith = function(str) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual.indexOf(str) === 0; + }, + + describeTo: function(description) { + description.append('starts with ').appendLiteral(str); + } + }); +}; + +/** + * The actual string must end with the given string. + */ +JsHamcrest.Matchers.endsWith = function(str) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual.lastIndexOf(str) + str.length == actual.length; + }, + + describeTo: function(description) { + description.append('ends with ').appendLiteral(str); + } + }); +}; + +/** + * The actual string must match the given regular expression. + */ +JsHamcrest.Matchers.matches = function(regex) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return regex.test(actual); + }, + + describeTo: function(description) { + description.append('matches ').appendLiteral(regex); + } + }); +}; + +/** + * The actual string must look like an e-mail address. + */ +JsHamcrest.Matchers.emailAddress = function() { + var regex = /^([a-z0-9_\.\-\+])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+$/i; + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return regex.test(actual); + }, + + describeTo: function(description) { + description.append('email address'); + } + }); +}; + +/** + * The actual value has a member with the given name. + */ +JsHamcrest.Matchers.hasMember = function(memberName, matcherOrValue) { + var undefined; + if (matcherOrValue === undefined) { + matcherOrValue = JsHamcrest.Matchers.anything(); + } else if (!JsHamcrest.isMatcher(matcherOrValue)) { + matcherOrValue = JsHamcrest.Matchers.equalTo(matcherOrValue); + } + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + if (actual && memberName in actual) { + return matcherOrValue.matches(actual[memberName]); + } + return false; + }, + + describeTo: function(description) { + description.append('has member ').appendLiteral(memberName) + .append(' (').appendDescriptionOf(matcherOrValue).append(')'); + } + }); +}; + +/** + * The actual value has a function with the given name. + */ +JsHamcrest.Matchers.hasFunction = function(functionName) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + if (actual) { + return functionName in actual && + actual[functionName] instanceof Function; + } + return false; + }, + + describeTo: function(description) { + description.append('has function ').appendLiteral(functionName); + } + }); +}; + +/** + * The actual value must be an instance of the given class. + */ +JsHamcrest.Matchers.instanceOf = function(clazz) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return !!(actual instanceof clazz); + }, + + describeTo: function(description) { + var className = clazz.name ? clazz.name : 'a class'; + description.append('instance of ').append(className); + } + }); +}; + +/** + * The actual value must be an instance of the given type. + */ +JsHamcrest.Matchers.typeOf = function(typeName) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return (typeof actual == typeName); + }, + + describeTo: function(description) { + description.append('typeof ').append('"').append(typeName).append('"'); + } + }); +}; + +/** + * The actual value must be an object. + */ +JsHamcrest.Matchers.object = function() { + return new JsHamcrest.Matchers.instanceOf(Object); +}; + +/** + * The actual value must be a string. + */ +JsHamcrest.Matchers.string = function() { + return new JsHamcrest.Matchers.typeOf('string'); +}; + +/** + * The actual value must be a number. + */ +JsHamcrest.Matchers.number = function() { + return new JsHamcrest.Matchers.typeOf('number'); +}; + +/** + * The actual value must be a boolean. + */ +JsHamcrest.Matchers.bool = function() { + return new JsHamcrest.Matchers.typeOf('boolean'); +}; + +/** + * The actual value must be a function. + */ +JsHamcrest.Matchers.func = function() { + return new JsHamcrest.Matchers.instanceOf(Function); +}; + +/** + * The actual value should be an array and it must contain at least one value + * that matches the given value or matcher. + */ +JsHamcrest.Matchers.hasItem = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + // Should be an array + if (!(actual instanceof Array)) { + return false; + } + + for (var i = 0; i < actual.length; i++) { + if (matcher.matches(actual[i])) { + return true; + } + } + return false; + }, + + describeTo: function(description) { + description.append('array contains item ') + .appendDescriptionOf(matcher); + } + }); +}); + +/** + * The actual value should be an array and the given values or matchers must + * match at least one item. + */ +JsHamcrest.Matchers.hasItems = function() { + var items = []; + for (var i = 0; i < arguments.length; i++) { + items.push(JsHamcrest.Matchers.hasItem(arguments[i])); + } + return JsHamcrest.Matchers.allOf(items); +}; + +/** + * The actual value should be an array and the given value or matcher must + * match all items. + */ +JsHamcrest.Matchers.everyItem = JsHamcrest.EqualTo(function(matcher) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + // Should be an array + if (!(actual instanceof Array)) { + return false; + } + + for (var i = 0; i < actual.length; i++) { + if (!matcher.matches(actual[i])) { + return false; + } + } + return true; + }, + + describeTo: function(description) { + description.append('every item ') + .appendDescriptionOf(matcher); + } + }); +}); + +/** + * The given array must contain the actual value. + */ +JsHamcrest.Matchers.isIn = function() { + var equalTo = JsHamcrest.Matchers.equalTo; + + var args = arguments; + if (args[0] instanceof Array) { + args = args[0]; + } + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + for (var i = 0; i < args.length; i++) { + if (equalTo(args[i]).matches(actual)) { + return true; + } + } + return false; + }, + + describeTo: function(description) { + description.append('one of ').appendLiteral(args); + } + }); +}; + +/** + * Alias to 'isIn' matcher. + */ +JsHamcrest.Matchers.oneOf = JsHamcrest.Matchers.isIn; + +/** + * The actual value should be an array and it must be empty to be sucessful. + */ +JsHamcrest.Matchers.empty = function() { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return actual.length === 0; + }, + + describeTo: function(description) { + description.append('empty'); + } + }); +}; + +/** + * The length of the actual value value must match the given value or matcher. + */ +JsHamcrest.Matchers.hasSize = JsHamcrest.EqualTo(function(matcher) { + var getSize = function(actual) { + var size = actual.length; + if (size === undefined && typeof actual === 'object') { + size = 0; + for (var key in actual) + size++; + } + return size; + }; + + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) { + return matcher.matches(getSize(actual)); + }, + + describeTo: function(description) { + description.append('has size ').appendDescriptionOf(matcher); + }, + + describeValueTo: function(actual, description) { + description.append(getSize(actual)); + } + }); +}); + +JsHamcrest.Matchers.equivalentMap = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) + { + if(JsHamcrest.retreiveEntityMatcherFunction(actual) === JsHamcrest.areMapsEquivalent && + JsHamcrest.retreiveEntityMatcherFunction(expected) === JsHamcrest.areMapsEquivalent) + { + return JsHamcrest.areMapsEquivalent(expected, actual); + } + + return false; //The passed in objects aren't maps. + }, + + describeTo: function(description) { + description.append('map equivalent to ').appendLiteral(expected); + } + }); +}; + +JsHamcrest.Matchers.equivalentArray = function(expected) { + return new JsHamcrest.SimpleMatcher({ + matches: function(actual) + { + if (expected instanceof Array && actual instanceof Array) + { + return JsHamcrest.areArraysEquivalent(expected, actual); + } + + return false; //The passed in objects aren't Arrays. + }, + + describeTo: function(description) { + description.append('array equivalent to ').appendLiteral(expected); + } + }); +}; +JsHamcrest.Operators = {}; + +/** + * Returns those items of the array for which matcher matches. + */ +JsHamcrest.Operators.filter = function(array, matcherOrValue) { + if (!(array instanceof Array) || matcherOrValue == null) { + return array; + } + if (!(matcherOrValue instanceof JsHamcrest.SimpleMatcher)) { + matcherOrValue = JsHamcrest.Matchers.equalTo(matcherOrValue); + } + + var result = []; + for (var i = 0; i < array.length; i++) { + if (matcherOrValue.matches(array[i])) { + result.push(array[i]); + } + } + return result; +}; + +/** + * Generic assert function. + */ +JsHamcrest.Operators.assert = function(actualValue, matcherOrValue, options) { + options = options ? options : {}; + var description = new JsHamcrest.Description(); + + if (matcherOrValue == null) { + matcherOrValue = JsHamcrest.Matchers.truth(); + } else if (!JsHamcrest.isMatcher(matcherOrValue)) { + matcherOrValue = JsHamcrest.Matchers.equalTo(matcherOrValue); + } + + if (options.message) { + description.append(options.message).append('. '); + } + + description.append('Expected '); + matcherOrValue.describeTo(description); + + if (!matcherOrValue.matches(actualValue)) { + description.passed = false; + description.append(' but was '); + matcherOrValue.describeValueTo(actualValue, description); + if (options.fail) { + options.fail(description.get()); + } + } else { + description.append(': Success'); + description.passed = true; + if (options.pass) { + options.pass(description.get()); + } + } + return description; +}; + +/** + * Delegate function, useful when used along with raises() and raisesAnything(). + */ +JsHamcrest.Operators.callTo = function() { + var func = [].shift.call(arguments); + var args = arguments; + return function() { + return func.apply(this, args); + }; +} + +/** + * Integration utilities. + */ + +JsHamcrest.Integration = (function() { + + var self = this; + + return { + + /** + * Copies all members of an object to another. + */ + copyMembers: function(source, target) { + if (arguments.length == 1) { + target = source; + JsHamcrest.Integration.copyMembers(JsHamcrest.Matchers, target); + JsHamcrest.Integration.copyMembers(JsHamcrest.Operators, target); + } else if (source) { + for (var method in source) { + if (!(method in target)) { + target[method] = source[method]; + } + } + } + }, + + /** + * Adds the members of the given object to JsHamcrest.Matchers + * namespace. + */ + installMatchers: function(matchersNamespace) { + var target = JsHamcrest.Matchers; + JsHamcrest.Integration.copyMembers(matchersNamespace, target); + }, + + /** + * Adds the members of the given object to JsHamcrest.Operators + * namespace. + */ + installOperators: function(operatorsNamespace) { + var target = JsHamcrest.Operators; + JsHamcrest.Integration.copyMembers(operatorsNamespace, target); + }, + + /** + * Uses the web browser's alert() function to display the assertion + * results. Great for quick prototyping. + */ + WebBrowser: function() { + JsHamcrest.Integration.copyMembers(self); + + self.assertThat = function (actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + alert('[FAIL] ' + message); + }, + pass: function(message) { + alert('[SUCCESS] ' + message); + } + }); + }; + }, + + /** + * Uses the Rhino's print() function to display the assertion results. + * Great for prototyping. + */ + Rhino: function() { + JsHamcrest.Integration.copyMembers(self); + + self.assertThat = function (actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + print('[FAIL] ' + message + '\n'); + }, + pass: function(message) { + print('[SUCCESS] ' + message + '\n'); + } + }); + }; + }, + + /** + * JsTestDriver integration. + */ + JsTestDriver: function(params) { + params = params ? params : {}; + var target = params.scope || self; + + JsHamcrest.Integration.copyMembers(target); + + // Function called when an assertion fails. + function fail(message) { + var exc = new Error(message); + exc.name = 'AssertError'; + + try { + // Removes all jshamcrest-related entries from error stack + var re = new RegExp('jshamcrest.*\.js\:', 'i'); + var stack = exc.stack.split('\n'); + var newStack = ''; + for (var i = 0; i < stack.length; i++) { + if (!re.test(stack[i])) { + newStack += stack[i] + '\n'; + } + } + exc.stack = newStack; + } catch (e) { + // It's okay, do nothing + } + throw exc; + } + + // Assertion method exposed to JsTestDriver. + target.assertThat = function (actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: fail + }); + }; + }, + + /** + * NodeUnit (Node.js Unit Testing) integration. + */ + + Nodeunit: function(params) { + params = params ? params : {}; + var target = params.scope || global; + + JsHamcrest.Integration.copyMembers(target); + + target.assertThat = function(actual, matcher, message, test) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + test.ok(false, message); + }, + pass: function(message) { + test.ok(true, message); + } + }); + }; + }, + + /** + * JsUnitTest integration. + */ + JsUnitTest: function(params) { + params = params ? params : {}; + var target = params.scope || JsUnitTest.Unit.Testcase.prototype; + + JsHamcrest.Integration.copyMembers(target); + + // Assertion method exposed to JsUnitTest. + target.assertThat = function (actual, matcher, message) { + var self = this; + + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + self.fail(message); + }, + pass: function() { + self.pass(); + } + }); + }; + }, + + /** + * YUITest (Yahoo UI) integration. + */ + YUITest: function(params) { + params = params ? params : {}; + var target = params.scope || self; + + JsHamcrest.Integration.copyMembers(target); + + target.Assert = YAHOO.util.Assert; + + // Assertion method exposed to YUITest. + YAHOO.util.Assert.that = function(actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + YAHOO.util.Assert.fail(message); + } + }); + }; + }, + + /** + * QUnit (JQuery) integration. + */ + QUnit: function(params) { + params = params ? params : {}; + var target = params.scope || self; + + JsHamcrest.Integration.copyMembers(target); + + // Assertion method exposed to QUnit. + target.assertThat = function(assert, actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + assert.ok(false, message); + }, + pass: function(message) { + assert.ok(true, message); + } + }); + }; + }, + + /** + * jsUnity integration. + */ + jsUnity: function(params) { + params = params ? params : {}; + var target = params.scope || jsUnity.env.defaultScope; + var assertions = params.attachAssertions || false; + + JsHamcrest.Integration.copyMembers(target); + + if (assertions) { + jsUnity.attachAssertions(target); + } + + // Assertion method exposed to jsUnity. + target.assertThat = function(actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + throw message; + } + }); + }; + }, + + /** + * Screw.Unit integration. + */ + screwunit: function(params) { + params = params ? params : {}; + var target = params.scope || Screw.Matchers; + + JsHamcrest.Integration.copyMembers(target); + + // Assertion method exposed to Screw.Unit. + target.assertThat = function(actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + throw message; + } + }); + }; + }, + + /** + * Jasmine integration. + */ + jasmine: function(params) { + params = params ? params : {}; + var target = params.scope || self; + + JsHamcrest.Integration.copyMembers(target); + + // Assertion method exposed to Jasmine. + target.assertThat = function(actual, matcher, message) { + return JsHamcrest.Operators.assert(actual, matcher, { + message: message, + fail: function(message) { + jasmine.getEnv().currentSpec.addMatcherResult( + new jasmine.ExpectationResult({passed:false, message:message}) + ); + }, + pass: function(message) { + jasmine.getEnv().currentSpec.addMatcherResult( + new jasmine.ExpectationResult({passed:true, message:message}) + ); + } + }); + }; + } + }; +})(); + +if (typeof exports !== "undefined") exports.JsHamcrest = JsHamcrest; \ No newline at end of file diff --git a/docs/SymptomsCausedByCOVIDLots/test/jsmockito-1.0.4.js b/docs/SymptomsCausedByCOVIDLots/test/jsmockito-1.0.4.js new file mode 100644 index 00000000000..3fe2a12f12f --- /dev/null +++ b/docs/SymptomsCausedByCOVIDLots/test/jsmockito-1.0.4.js @@ -0,0 +1,1019 @@ +/* vi:ts=2 sw=2 expandtab + * + * JsMockito v1.0.4 + * http://github.com/chrisleishman/jsmockito + * + * Mockito port to JavaScript + * + * Copyright (c) 2009 Chris Leishman + * Licensed under the BSD license + */ + +/** + * Main namespace. + * @namespace + * + *

Contents

+ * + *
    + *
  1. Let's verify some behaviour!
  2. + *
  3. How about some stubbing?
  4. + *
  5. Matching Arguments
  6. + *
  7. Verifying exact number of invocations / at least once / + * never
  8. + *
  9. Matching the context ('this')
  10. + *
  11. Making sure interactions never happened on a mock
  12. + *
  13. Finding redundant invocations
  14. + *
+ * + *

In the following examples object mocking is done with Array as this is + * well understood, although you probably wouldn't mock this in normal test + * development.

+ * + *

1. Let's verify some behaviour!

+ * + *

For an object:

+ *
+ * //mock creation
+ * var mockedArray = mock(Array);
+ *
+ * //using mock object
+ * mockedArray.push("one");
+ * mockedArray.reverse();
+ *
+ * //verification
+ * verify(mockedArray).push("one");
+ * verify(mockedArray).reverse();
+ * 
+ * + *

For a function:

+ *
+ * //mock creation
+ * var mockedFunc = mockFunction();
+ *
+ * //using mock function
+ * mockedFunc('hello world');
+ * mockedFunc.call(this, 'foobar');
+ * mockedFunc.apply(this, [ 'barfoo' ]);
+ *
+ * //verification
+ * verify(mockedFunc)('hello world');
+ * verify(mockedFunc)('foobar');
+ * verify(mockedFunc)('barfoo');
+ * 
+ * + *

Once created a mock will remember all interactions. Then you selectively + * verify whatever interactions you are interested in.

+ * + *

2. How about some stubbing?

+ * + *

For an object:

+ *
+ * var mockedArray = mock(Array);
+ *
+ * //stubbing
+ * when(mockedArray).slice(0).thenReturn('f');
+ * when(mockedArray).slice(1).thenThrow('An exception');
+ * when(mockedArray).slice(2).then(function() { return 1+2 });
+ *
+ * //the following returns "f"
+ * assertThat(mockedArray.slice(0), equalTo('f'));
+ *
+ * //the following throws exception 'An exception'
+ * var ex = undefined;
+ * try {
+ *   mockedArray.slice(1);
+ * } catch (e) {
+ *   ex = e;
+ * }
+ * assertThat(ex, equalTo('An exception');
+ *
+ * //the following invokes the stub method, which returns 3
+ * assertThat(mockedArray.slice(2), equalTo(3));
+ *
+ * //the following returns undefined as slice(999) was not stubbed
+ * assertThat(mockedArray.slice(999), typeOf('undefined'));
+ *
+ * //stubs can take multiple values to return in order (same for 'thenThrow' and 'then' as well)
+ * when(mockedArray).pop().thenReturn('a', 'b', 'c');
+ * assertThat(mockedArray.pop(), equalTo('a'));
+ * assertThat(mockedArray.pop(), equalTo('b'));
+ * assertThat(mockedArray.pop(), equalTo('c'));
+ * assertThat(mockedArray.pop(), equalTo('c'));
+ *
+ * //stubs can also be chained to return values in order
+ * when(mockedArray).unshift().thenReturn('a').thenReturn('b').then(function() { return 'c' });
+ * assertThat(mockedArray.unshift(), equalTo('a'));
+ * assertThat(mockedArray.unshift(), equalTo('b'));
+ * assertThat(mockedArray.unshift(), equalTo('c'));
+ * assertThat(mockedArray.unshift(), equalTo('c'));
+ *
+ * //stub matching can overlap, allowing for specific cases and defaults
+ * when(mockedArray).slice(3).thenReturn('abcde');
+ * when(mockedArray).slice(3, lessThan(0)).thenReturn('edcba');
+ * assertThat(mockedArray.slice(3, -1), equalTo('edcba'));
+ * assertThat(mockedArray.slice(3, 1), equalTo('abcde'));
+ * assertThat(mockedArray.slice(3), equalTo('abcde'));
+ *
+ * //can also verify a stubbed invocation, although this is usually redundant
+ * verify(mockedArray).slice(0);
+ * 
+ * + *

For a function:

+ *
+ * var mockedFunc = mockFunction();
+ *
+ * //stubbing
+ * when(mockedFunc)(0).thenReturn('f');
+ * when(mockedFunc)(1).thenThrow('An exception');
+ * when(mockedFunc)(2).then(function() { return 1+2 });
+ *
+ * //the following returns "f"
+ * assertThat(mockedFunc(0), equalTo('f'))
+ *
+ * //following throws exception 'An exception'
+ * mockedFunc(1);
+ * //the following throws exception 'An exception'
+ * var ex = undefined;
+ * try {
+ *   mockedFunc(1);
+ * } catch (e) {
+ *   ex = e;
+ * }
+ * assertThat(ex, equalTo('An exception');
+ *
+ * //the following invokes the stub method, which returns 3
+ * assertThat(mockedFunc(2), equalTo(3));
+ *
+ * //following returns undefined as mockedFunc(999) was not stubbed
+ * assertThat(mockedFunc(999), typeOf('undefined'));
+ *
+ * //stubs can take multiple values to return in order (same for 'thenThrow' and 'then' as well)
+ * when(mockedFunc)(3).thenReturn('a', 'b', 'c');
+ * assertThat(mockedFunc(3), equalTo('a'));
+ * assertThat(mockedFunc(3), equalTo('b'));
+ * assertThat(mockedFunc(3), equalTo('c'));
+ * assertThat(mockedFunc(3), equalTo('c'));
+ *
+ * //stubs can also be chained to return values in order
+ * when(mockedFunc)(4).thenReturn('a').thenReturn('b').then(function() { return 'c' });
+ * assertThat(mockedFunc(4), equalTo('a'));
+ * assertThat(mockedFunc(4), equalTo('b'));
+ * assertThat(mockedFunc(4), equalTo('c'));
+ * assertThat(mockedFunc(4), equalTo('c'));
+ *
+ * //stub matching can overlap, allowing for specific cases and defaults
+ * when(mockedFunc)(5).thenReturn('abcde')
+ * when(mockedFunc)(5, lessThan(0)).thenReturn('edcba')
+ * assertThat(mockedFunc(5, -1), equalTo('edcba'))
+ * assertThat(mockedFunc(5, 1), equalTo('abcde'))
+ * assertThat(mockedFunc(5), equalTo('abcde'))
+ *
+ * //can also verify a stubbed invocation, although this is usually redundant
+ * verify(mockedFunc)(0);
+ * 
+ * + * + * + *

3. Matching Arguments

+ * + *

JsMockito verifies arguments using + * JsHamcrest matchers. + * + *

+ * var mockedArray = mock(Array);
+ * var mockedFunc = mockFunction();
+ *
+ * //stubbing using JsHamcrest
+ * when(mockedArray).slice(lessThan(10)).thenReturn('f');
+ * when(mockedFunc)(containsString('world')).thenReturn('foobar');
+ *
+ * //following returns "f"
+ * mockedArray.slice(5);
+ *
+ * //following returns "foobar"
+ * mockedFunc('hello world');
+ *
+ * //you can also use matchers in verification
+ * verify(mockedArray).slice(greaterThan(4));
+ * verify(mockedFunc)(equalTo('hello world'));
+ *
+ * //if not specified then the matcher is anything(), thus either of these
+ * //will match an invocation with a single argument
+ * verify(mockedFunc)();
+ * verify(mockedFunc)(anything());
+ * 
+ * + * + * + *

4. Verifying exact number of invocations / at least once / + * never

+ * + *
+ * var mockedArray = mock(Array);
+ * var mockedFunc = mockFunction();
+ *
+ * mockedArray.slice(5);
+ * mockedArray.slice(6);
+ * mockedFunc('a');
+ * mockedFunc('b');
+ *
+ * //verification of multiple matching invocations
+ * verify(mockedArray, times(2)).slice(anything());
+ * verify(mockedFunc, times(2))(anything());
+ *
+ * //the default is times(1), making these are equivalent
+ * verify(mockedArray, times(1)).slice(5);
+ * verify(mockedArray).slice(5);
+ * 
+ * + *

5. Matching the context ('this')

+ * + * Functions can be invoked with a specific context, using the 'call' or + * 'apply' methods. JsMockito mock functions (and mock object methods) + * will remember this context and verification/stubbing can match on it. + * + *

For a function:

+ *
+ * var mockedFunc = mockFunction();
+ * var context1 = {};
+ * var context2 = {};
+ *
+ * when(mockedFunc).call(equalTo(context2), anything()).thenReturn('hello');
+ *
+ * mockedFunc.call(context1, 'foo');
+ * //the following returns 'hello'
+ * mockedFunc.apply(context2, [ 'bar' ]);
+ *
+ * verify(mockedFunc).apply(context1, [ 'foo' ]);
+ * verify(mockedFunc).call(context2, 'bar');
+ * 
+ * + *

For object method invocations, the context is usually the object itself. + * But sometimes people do strange things, and you need to test it - so + * the same approach can be used for an object:

+ *
+ * var mockedArray = mock(Array);
+ * var otherContext = {};
+ *
+ * when(mockedArray).slice.call(otherContext, 5).thenReturn('h');
+ *
+ * //the following returns 'h'
+ * mockedArray.slice.apply(otherContext, [ 5 ]);
+ *
+ * verify(mockedArray).slice.call(equalTo(otherContext), 5);
+ * 
+ * + * + * + *

6. Making sure interactions never happened on a mock

+ * + *
+ * var mockOne = mock(Array);
+ * var mockTwo = mock(Array);
+ * var mockThree = mockFunction();
+ * 
+ * //only mockOne is interacted with
+ * mockOne.push(5);
+ *
+ * //verify a method was never called
+ * verify(mockOne, never()).unshift('a');
+ * 
+ * //verify that other mocks were not interacted with
+ * verifyZeroInteractions(mockTwo, mockThree);
+ * 
+ * + *

7. Finding redundant invocations

+ * + *
+ * var mockArray = mock(Array);
+ * 
+ * mockArray.push(5);
+ * mockArray.push(8);
+ *
+ * verify(mockArray).push(5);
+ *
+ * // following verification will fail
+ * verifyNoMoreInteractions(mockArray);
+ * 
+ */ +JsMockito = { + /** + * Library version, + */ + version: '1.0.4', + + _export: ['isMock', 'when', 'verify', 'verifyZeroInteractions', + 'verifyNoMoreInteractions', 'spy'], + + /** + * Test if a given variable is a mock + * + * @param maybeMock An object + * @return {boolean} true if the variable is a mock + */ + isMock: function(maybeMock) { + return typeof maybeMock._jsMockitoVerifier != 'undefined'; + }, + + /** + * Add a stub for a mock object method or mock function + * + * @param mock A mock object or mock anonymous function + * @return {object or function} A stub builder on which the method or + * function to be stubbed can be invoked + */ + when: function(mock) { + return mock._jsMockitoStubBuilder(); + }, + + /** + * Verify that a mock object method or mock function was invoked + * + * @param mock A mock object or mock anonymous function + * @param verifier Optional JsMockito.Verifier instance (default: JsMockito.Verifiers.once()) + * @return {object or function} A verifier on which the method or function to + * be verified can be invoked + */ + verify: function(mock, verifier) { + return (verifier || JsMockito.Verifiers.once()).verify(mock); + }, + + /** + * Verify that no mock object methods or the mock function were ever invoked + * + * @param mock A mock object or mock anonymous function (multiple accepted) + */ + verifyZeroInteractions: function() { + JsMockito.each(arguments, function(mock) { + JsMockito.Verifiers.zeroInteractions().verify(mock); + }); + }, + + /** + * Verify that no mock object method or mock function invocations remain + * unverified + * + * @param mock A mock object or mock anonymous function (multiple accepted) + */ + verifyNoMoreInteractions: function() { + JsMockito.each(arguments, function(mock) { + JsMockito.Verifiers.noMoreInteractions().verify(mock); + }); + }, + + /** + * Create a mock that proxies a real function or object. All un-stubbed + * invocations will be passed through to the real implementation, but can + * still be verified. + * + * @param {object or function} delegate A 'real' (concrete) object or + * function that the mock will delegate unstubbed invocations to + * @return {object or function} A mock object (as per mock) or mock function + * (as per mockFunction) + */ + spy: function(delegate) { + return (typeof delegate == 'function')? + JsMockito.mockFunction(delegate) : JsMockito.mock(delegate); + }, + + contextCaptureFunction: function(defaultContext, handler) { + // generate a function with overridden 'call' and 'apply' methods + // and apply a default context when these are not used to supply + // one explictly. + var captureFunction = function() { + return captureFunction.apply(defaultContext, + Array.prototype.slice.call(arguments, 0)); + }; + captureFunction.call = function(context) { + return captureFunction.apply(context, + Array.prototype.slice.call(arguments, 1)); + }; + captureFunction.apply = function(context, args) { + return handler.apply(this, [ context, args||[] ]); + }; + return captureFunction; + }, + + matchArray: function(matchers, array) { + if (matchers.length > array.length) + return false; + return !JsMockito.any(matchers, function(matcher, i) { + return !matcher.matches(array[i]); + }); + }, + + toMatcher: function(obj) { + return JsHamcrest.isMatcher(obj)? obj : + JsHamcrest.Matchers.equalTo(obj); + }, + + mapToMatchers: function(srcArray) { + return JsMockito.map(srcArray, function(obj) { + return JsMockito.toMatcher(obj); + }); + }, + + verifier: function(name, proto) { + JsMockito.Verifiers[name] = function() { JsMockito.Verifier.apply(this, arguments) }; + JsMockito.Verifiers[name].prototype = new JsMockito.Verifier; + JsMockito.Verifiers[name].prototype.constructor = JsMockito.Verifiers[name]; + JsMockito.extend(JsMockito.Verifiers[name].prototype, proto); + }, + + each: function(srcArray, callback) { + for (var i = 0; i < srcArray.length; i++) + callback(srcArray[i], i); + }, + + eachObject: function(srcObject, callback) { + for (var key in srcObject) + callback(srcObject[key], key); + }, + + extend: function(dstObject, srcObject) { + for (var prop in srcObject) { + dstObject[prop] = srcObject[prop]; + } + return dstObject; + }, + + objectKeys: function(srcObject) { + var result = []; + JsMockito.eachObject(srcObject, function(elem, key) { + result.push(key); + }); + return result; + }, + + objectValues: function(srcObject) { + var result = []; + JsMockito.eachObject(srcObject, function(elem, key) { + result.push(elem); + }); + return result; + }, + + map: function(srcArray, callback) { + var result = []; + JsMockito.each(srcArray, function(elem, key) { + var val = callback(elem, key); + if (val != null) + result.push(val); + }); + return result; + }, + + mapObject: function(srcObject, callback) { + var result = {}; + JsMockito.eachObject(srcObject, function(elem, key) { + var val = callback(elem, key); + if (val != null) + result[key] = val; + }); + return result; + }, + + mapInto: function(dstObject, srcObject, callback) { + return JsMockito.extend(dstObject, + JsMockito.mapObject(srcObject, function(elem, key) { + return callback(elem, key); + }) + ); + }, + + grep: function(srcArray, callback) { + var result = []; + JsMockito.each(srcArray, function(elem, key) { + if (callback(elem, key)) + result.push(elem); + }); + return result; + }, + + find: function(array, callback) { + for (var i = 0; i < array.length; i++) + if (callback(array[i], i)) + return array[i]; + return undefined; + }, + + any: function(array, callback) { + return (this.find(array, callback) != undefined); + } +}; + + +/** + * Create a mockable and stubbable anonymous function. + * + *

Once created, the function can be invoked and will return undefined for + * any interactions that do not match stub declarations.

+ * + *
+ * var mockFunc = JsMockito.mockFunction();
+ * JsMockito.when(mockFunc).call(anything(), 1, 5).thenReturn(6);
+ * mockFunc(1, 5); // result is 6
+ * JsMockito.verify(mockFunc)(1, greaterThan(2));
+ * 
+ * + * @param funcName {string} The name of the mock function to use in messages + * (defaults to 'func') + * @param delegate {function} The function to delegate unstubbed calls to + * (optional) + * @return {function} an anonymous function + */ +JsMockito.mockFunction = function(funcName, delegate) { + if (typeof funcName == 'function') { + delegate = funcName; + funcName = undefined; + } + funcName = funcName || 'func'; + delegate = delegate || function() { }; + + var stubMatchers = [] + var interactions = []; + + var mockFunc = function() { + var args = [this]; + args.push.apply(args, arguments); + interactions.push({args: args}); + + var stubMatcher = JsMockito.find(stubMatchers, function(stubMatcher) { + return JsMockito.matchArray(stubMatcher[0], args); + }); + if (stubMatcher == undefined) + return delegate.apply(this, arguments); + var stubs = stubMatcher[1]; + if (stubs.length == 0) + return undefined; + var stub = stubs[0]; + if (stubs.length > 1) + stubs.shift(); + return stub.apply(this, arguments); + }; + + mockFunc.prototype = delegate.prototype; + + mockFunc._jsMockitoStubBuilder = function(contextMatcher) { + var contextMatcher = contextMatcher || JsHamcrest.Matchers.anything(); + return matcherCaptureFunction(contextMatcher, function(matchers) { + var stubMatch = [matchers, []]; + stubMatchers.unshift(stubMatch); + return { + then: function() { + stubMatch[1].push.apply(stubMatch[1], arguments); + return this; + }, + thenReturn: function() { + return this.then.apply(this,JsMockito.map(arguments, function(value) { + return function() { return value }; + })); + }, + thenThrow: function(exception) { + return this.then.apply(this,JsMockito.map(arguments, function(value) { + return function() { throw value }; + })); + } + }; + }); + }; + + mockFunc._jsMockitoVerifier = function(verifier, contextMatcher) { + var contextMatcher = contextMatcher || JsHamcrest.Matchers.anything(); + return matcherCaptureFunction(contextMatcher, function(matchers) { + return verifier(funcName, interactions, matchers, matchers[0] != contextMatcher); + }); + }; + + mockFunc._jsMockitoMockFunctions = function() { + return [ mockFunc ]; + }; + + return mockFunc; + + function matcherCaptureFunction(contextMatcher, handler) { + return JsMockito.contextCaptureFunction(contextMatcher, + function(context, args) { + var matchers = JsMockito.mapToMatchers([context].concat(args || [])); + return handler(matchers); + } + ); + }; +}; +JsMockito._export.push('mockFunction'); + + +/** + * Create a mockable and stubbable objects. + * + *

A mock is created with the constructor for an object as an argument. + * Once created, the mock object will have all the same methods as the source + * object which, when invoked, will return undefined by default.

+ * + *

Stub declarations may then be made for these methods to have them return + * useful values or perform actions when invoked.

+ * + *
+ * MyObject = function() {
+ *   this.add = function(a, b) { return a + b }
+ * };
+ *
+ * var mockObj = JsMockito.mock(MyObject);
+ * mockObj.add(5, 4); // result is undefined
+ *
+ * JsMockito.when(mockFunc).add(1, 2).thenReturn(6);
+ * mockObj.add(1, 2); // result is 6
+ *
+ * JsMockito.verify(mockObj).add(1, greaterThan(2)); // ok
+ * JsMockito.verify(mockObj).add(1, equalTo(2)); // ok
+ * JsMockito.verify(mockObj).add(1, 4); // will throw an exception
+ * 
+ * + * @param Obj {function} the constructor for the object to be mocked + * @return {object} a mock object + */ +JsMockito.mock = function(Obj) { + var delegate = {}; + if (typeof Obj != "function") { + delegate = Obj; + Obj = function() { }; + Obj.prototype = delegate; + Obj.prototype.constructor = Obj; + } + var MockObject = function() { }; + MockObject.prototype = new Obj; + MockObject.prototype.constructor = MockObject; + + var mockObject = new MockObject(); + var stubBuilders = {}; + var verifiers = {}; + + var contextMatcher = JsHamcrest.Matchers.sameAs(mockObject); + + var addMockMethod = function(name) { + var delegateMethod; + if (delegate[name] != undefined) { + delegateMethod = function() { + var context = (this == mockObject)? delegate : this; + return delegate[name].apply(context, arguments); + }; + } + mockObject[name] = JsMockito.mockFunction('obj.' + name, delegateMethod); + stubBuilders[name] = mockObject[name]._jsMockitoStubBuilder; + verifiers[name] = mockObject[name]._jsMockitoVerifier; + }; + + for (var methodName in mockObject) { + if (methodName != 'constructor') + addMockMethod(methodName); + } + + for (var typeName in JsMockito.nativeTypes) { + if (mockObject instanceof JsMockito.nativeTypes[typeName].type) { + JsMockito.each(JsMockito.nativeTypes[typeName].methods, function(method) { + addMockMethod(method); + }); + } + } + + mockObject._jsMockitoStubBuilder = function() { + return JsMockito.mapInto(new MockObject(), stubBuilders, function(method) { + return method.call(this, contextMatcher); + }); + }; + + mockObject._jsMockitoVerifier = function(verifier) { + return JsMockito.mapInto(new MockObject(), verifiers, function(method) { + return method.call(this, verifier, contextMatcher); + }); + }; + + mockObject._jsMockitoMockFunctions = function() { + return JsMockito.objectValues( + JsMockito.mapObject(mockObject, function(func) { + return JsMockito.isMock(func)? func : null; + }) + ); + }; + + return mockObject; +}; +JsMockito._export.push('mock'); + +JsMockito.nativeTypes = { + 'Array': { + type: Array, + methods: [ + 'concat', 'join', 'pop', 'push', 'reverse', 'shift', 'slice', 'sort', + 'splice', 'toString', 'unshift', 'valueOf' + ] + }, + 'Boolean': { + type: Boolean, + methods: [ + 'toString', 'valueOf' + ] + }, + 'Date': { + type: Date, + methods: [ + 'getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds', + 'getMinutes', 'getMonth', 'getSeconds', 'getTime', 'getTimezoneOffset', + 'getUTCDate', 'getUTCDay', 'getUTCMonth', 'getUTCFullYear', + 'getUTCHours', 'getUTCMinutes', 'getUTCSeconds', 'getUTCMilliseconds', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', + 'setUTCMonth', 'setUTCFullYear', 'setUTCHours', 'setUTCMinutes', + 'setUTCSeconds', 'setUTCMilliseconds', 'setYear', 'toDateString', + 'toGMTString', 'toLocaleDateString', 'toLocaleTimeString', + 'toLocaleString', 'toString', 'toTimeString', 'toUTCString', + 'valueOf' + ] + }, + 'Number': { + type: Number, + methods: [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision', 'toString', + 'valueOf' + ] + }, + 'String': { + type: String, + methods: [ + 'anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'concat', + 'fixed', 'fontcolor', 'fontsize', 'indexOf', 'italics', + 'lastIndexOf', 'link', 'match', 'replace', 'search', 'slice', 'small', + 'split', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLowerCase', + 'toUpperCase', 'valueOf' + ] + }, + 'RegExp': { + type: RegExp, + methods: [ + 'compile', 'exec', 'test' + ] + } +}; + + +/** + * Verifiers + * @namespace + */ +JsMockito.Verifiers = { + _export: ['never', 'zeroInteractions', 'noMoreInteractions', 'times', 'once'], + + /** + * Test that a invocation never occurred. For example: + *
+   * verify(mock, never()).method();
+   * 
+ * @see JsMockito.Verifiers.times(0) + */ + never: function() { + return new JsMockito.Verifiers.Times(0); + }, + + /** Test that no interaction were made on the mock. For example: + *
+   * verify(mock, zeroInteractions());
+   * 
+ * @see JsMockito.verifyZeroInteractions() + */ + zeroInteractions: function() { + return new JsMockito.Verifiers.ZeroInteractions(); + }, + + /** Test that no further interactions remain unverified on the mock. For + * example: + *
+   * verify(mock, noMoreInteractions());
+   * 
+ * @see JsMockito.verifyNoMoreInteractions() + */ + noMoreInteractions: function() { + return new JsMockito.Verifiers.NoMoreInteractions(); + }, + + /** + * Test that an invocation occurred a specific number of times. For example: + *
+   * verify(mock, times(2)).method();
+   * 
+ * + * @param wanted The number of desired invocations + */ + times: function(wanted) { + return new JsMockito.Verifiers.Times(wanted); + }, + + /** + * Test that an invocation occurred exactly once. For example: + *
+   * verify(mock, once()).method();
+   * 
+ * This is the default verifier. + * @see JsMockito.Verifiers.times(1) + */ + once: function() { + return new JsMockito.Verifiers.Times(1); + } +}; + +JsMockito.Verifier = function() { this.init.apply(this, arguments) }; +JsMockito.Verifier.prototype = { + init: function() { }, + + verify: function(mock) { + var self = this; + return mock._jsMockitoVerifier(function() { + self.verifyInteractions.apply(self, arguments); + }); + }, + + verifyInteractions: function(funcName, interactions, matchers, describeContext) { + }, + + updateVerifiedInteractions: function(interactions) { + JsMockito.each(interactions, function(interaction) { + interaction.verified = true; + }); + }, + + buildDescription: function(message, funcName, matchers, describeContext) { + var description = new JsHamcrest.Description(); + description.append(message + ': ' + funcName + '('); + JsMockito.each(matchers.slice(1), function(matcher, i) { + if (i > 0) + description.append(', '); + description.append('<'); + matcher.describeTo(description); + description.append('>'); + }); + description.append(")"); + if (describeContext) { + description.append(", 'this' being "); + matchers[0].describeTo(description); + } + return description; + } +}; + +JsMockito.verifier('Times', { + init: function(wanted) { + this.wanted = wanted; + }, + + verifyInteractions: function(funcName, allInteractions, matchers, describeContext) { + var interactions = JsMockito.grep(allInteractions, function(interaction) { + return JsMockito.matchArray(matchers, interaction.args); + }); + if (interactions.length == this.wanted) { + this.updateVerifiedInteractions(interactions); + return; + } + + var message; + if (interactions.length == 0) { + message = 'Wanted but not invoked'; + } else if (this.wanted == 0) { + message = 'Never wanted but invoked'; + } else if (this.wanted == 1) { + message = 'Wanted 1 invocation but got ' + interactions.length; + } else { + message = 'Wanted ' + this.wanted + ' invocations but got ' + interactions.length; + } + + var description = this.buildDescription(message, funcName, matchers, describeContext); + throw description.get(); + } +}); + +JsMockito.verifier('ZeroInteractions', { + verify: function(mock) { + var neverVerifier = JsMockito.Verifiers.never(); + JsMockito.each(mock._jsMockitoMockFunctions(), function(mockFunc) { + neverVerifier.verify(mockFunc)(); + }); + } +}); + +JsMockito.verifier('NoMoreInteractions', { + verify: function(mock) { + var self = this; + JsMockito.each(mock._jsMockitoMockFunctions(), function(mockFunc) { + JsMockito.Verifier.prototype.verify.call(self, mockFunc)(); + }); + }, + + verifyInteractions: function(funcName, allInteractions, matchers, describeContext) { + var interactions = JsMockito.grep(allInteractions, function(interaction) { + return interaction.verified != true; + }); + if (interactions.length == 0) + return; + + var description = this.buildDescription( + "No interactions wanted, but " + interactions.length + " remains", + funcName, matchers, describeContext); + throw description.get(); + } +}); + + +/** + * Verifiers + * @namespace + */ +JsMockito.Integration = { + /** + * Import the public JsMockito API into the specified object (namespace) + * + * @param {object} target An object (namespace) that will be populated with + * the functions from the public JsMockito API + */ + importTo: function(target) { + JsMockito.each(JsMockito._export, function(exported) { + target[exported] = JsMockito[exported]; + }); + + JsMockito.each(JsMockito.Verifiers._export, function(exported) { + target[exported] = JsMockito.Verifiers[exported]; + }); + }, + + /** + * Make the public JsMockito API available in Screw.Unit + * @see JsMockito.Integration.importTo(Screw.Matchers) + */ + screwunit: function() { + JsMockito.Integration.importTo(Screw.Matchers); + }, + + /** + * Make the public JsMockito API available to JsTestDriver + * @see JsMockito.Integration.importTo(window) + */ + JsTestDriver: function() { + JsMockito.Integration.importTo(window); + }, + + /** + * Make the public JsMockito API available to JsUnitTest + * @see JsMockito.Integration.importTo(JsUnitTest.Unit.Testcase.prototype) + */ + JsUnitTest: function() { + JsMockito.Integration.importTo(JsUnitTest.Unit.Testcase.prototype); + }, + + /** + * Make the public JsMockito API available to YUITest + * @see JsMockito.Integration.importTo(window) + */ + YUITest: function() { + JsMockito.Integration.importTo(window); + }, + + /** + * Make the public JsMockito API available to QUnit + * @see JsMockito.Integration.importTo(window) + */ + QUnit: function() { + JsMockito.Integration.importTo(window); + }, + + /** + * Make the public JsMockito API available to jsUnity + * @see JsMockito.Integration.importTo(jsUnity.env.defaultScope) + */ + jsUnity: function() { + JsMockito.Integration.importTo(jsUnity.env.defaultScope); + }, + + /** + * Make the public JsMockito API available to jSpec + * @see JsMockito.Integration.importTo(jSpec.defaultContext) + */ + jSpec: function() { + JsMockito.Integration.importTo(jSpec.defaultContext); + } +}; diff --git a/src/help.txt b/src/help.txt index ea5622c03e5..bbe99ad1f61 100644 --- a/src/help.txt +++ b/src/help.txt @@ -7,6 +7,7 @@ FK-TODO: - Symptomhistogramm - http://www.howbadismybatch.info/SymptomsCausedByVaccines/index.html - http://www.howbadismybatch.info/SymptomsCausedByCOVIDLots/index.html + http://www.howbadismybatch.info/SymptomsCausedByCOVIDLots/test/index.test.html anacron job: sudo cp src/intensivstationen_howbadismybatch.sh /etc/cron.daily/intensivstationen_howbadismybatch From 8b2b0d68b7e31b411f5e55c1cf45ec517d98231d Mon Sep 17 00:00:00 2001 From: frankknoll Date: Wed, 8 Nov 2023 22:31:08 +0100 Subject: [PATCH 03/16] refining SymptomVsSymptomChartDataProviderTest --- .../js/SymptomVsSymptomChartDataProvider.js | 26 ++++++++-- .../SymptomVsSymptomChartDataProviderTest.js | 47 ++++++++++++++----- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js index 68492d9ac8f..a59b72f1b6c 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js @@ -1,13 +1,31 @@ class SymptomVsSymptomChartDataProvider { - static retainCommonLots({ prrByLotX, prrByLotY }) { - const commonLots = SymptomVsSymptomChartDataProvider.#getCommonKeys(prrByLotX, prrByLotY); + // FK-TODO: extract utility class and test + static retainCommonKeys({ dict1, dict2 }) { + const commonKeys = SymptomVsSymptomChartDataProvider.#getCommonKeys(dict1, dict2); return { - prrByLotX: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(prrByLotX, commonLots), - prrByLotY: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(prrByLotY, commonLots) + dict1: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(dict1, commonKeys), + dict2: SymptomVsSymptomChartDataProvider.#retainKeysOfDict(dict2, commonKeys) }; } + static getChartData({ prrByLotX, prrByLotY }) { + const { dict1: prrByLotXCommon, dict2: prrByLotYCommon } = + SymptomVsSymptomChartDataProvider.retainCommonKeys( + { + dict1: prrByLotX, + dict2: prrByLotY + }); + return Object + .keys(prrByLotXCommon) + .map( + lot => + ({ + x: prrByLotXCommon[lot], + y: prrByLotYCommon[lot] + })); + } + static #getCommonKeys(dict1, dict2) { return Sets.intersection( SymptomVsSymptomChartDataProvider.#getKeySet(dict1), diff --git a/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js b/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js index 411c9dba08f..66fa15d6cf8 100644 --- a/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js +++ b/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js @@ -5,48 +5,73 @@ QUnit.module('SymptomVsSymptomChartDataProviderTest', function () { [ [ { - prrByLotX: { + dict1: { "lotX": 1.0 }, - prrByLotY: { + dict2: { "lotY": 2.0 } }, { - prrByLotX: { + dict1: { }, - prrByLotY: { + dict2: { } } ], [ { - prrByLotX: { + dict1: { "lotX": 1.0, "lotCommon": 2.0 }, - prrByLotY: { + dict2: { "lotCommon": 3.0, "lotY": 4.0 } }, { - prrByLotX: { + dict1: { "lotCommon": 2.0 }, - prrByLotY: { + dict2: { "lotCommon": 3.0 } } ] ], - (assert, [dataSets, mergedDataSets]) => { + (assert, [dicts, dictsHavingCommonKeys]) => { // Given // When - const mergedDataSetsActual = SymptomVsSymptomChartDataProvider.retainCommonLots(dataSets); + const dictsHavingCommonKeysActual = SymptomVsSymptomChartDataProvider.retainCommonKeys(dicts); // Then - assert.deepEqual(mergedDataSetsActual, mergedDataSets); + assert.deepEqual(dictsHavingCommonKeysActual, dictsHavingCommonKeys); }); + + QUnit.test('shouldProvideChartData', function (assert) { + // Given + const prrByLotX = { + "lotX": 1.0, + "lotCommon": 2.0 + }; + const prrByLotY = { + "lotCommon": 3.0, + "lotY": 4.0 + }; + + // When + const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); + + // Then + assert.deepEqual( + chartData, + [ + { + x: 2.0, + y: 3.0 + } + ]); + }); }); \ No newline at end of file From 010af053ab43acdcfe5920a21d0f32fa4042dc20 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Wed, 8 Nov 2023 22:37:09 +0100 Subject: [PATCH 04/16] displaying some scatter chart --- .../js/SymptomVsSymptomChartView.js | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index 52cc8c916c8..a71e8ee8dcd 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -7,30 +7,22 @@ class SymptomVsSymptomChartView { this.#canvas = canvas; } - displayChart(symptom1, symptom2) { + displayChart(symptomX, symptomY) { if (this.#chart != null) { this.#chart.destroy(); } // FK-TODO: fetch multiple files: https://stackoverflow.com/a/31711496 or https://stackoverflow.com/a/53892713 - PrrByVaccineProvider.getPrrByVaccine(symptom1) + PrrByVaccineProvider.getPrrByVaccine(symptomX) .then( - prrByLot1 => { - PrrByVaccineProvider.getPrrByVaccine(symptom2) + prrByLotX => { + PrrByVaccineProvider.getPrrByVaccine(symptomY) .then( - prrByLot2 => { - const myData = - Object - .values(prrByLot2) - .map( - val => - ({ - x: val, - y: val - })); + prrByLotY => { + const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); const data = { datasets: [{ label: 'Scatter Dataset', - data: myData, + data: chartData, backgroundColor: 'rgb(255, 99, 132)' }], }; From cfa314b4ae04877560db08412e1f5ab7ea4d7692 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 00:24:46 +0100 Subject: [PATCH 05/16] setting backgroundColor of scatter chart --- docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index a71e8ee8dcd..b3db01ea086 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -23,7 +23,7 @@ class SymptomVsSymptomChartView { datasets: [{ label: 'Scatter Dataset', data: chartData, - backgroundColor: 'rgb(255, 99, 132)' + backgroundColor: 'rgb(0, 0, 255)' }], }; const config = { From 46e3f4c22ddff50dec22d9259c39b5abd585c8e7 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 09:22:55 +0100 Subject: [PATCH 06/16] displaying batch lots as tooltips in scatter chart --- docs/SymptomsCausedByCOVIDLots/index.html | 1 + .../js/SymptomVsSymptomChartDataProvider.js | 18 ++++++++++-------- .../js/SymptomVsSymptomChartView.js | 17 +++++++++++++++-- .../SymptomVsSymptomChartDataProviderTest.js | 15 +++++++++------ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/index.html b/docs/SymptomsCausedByCOVIDLots/index.html index d8ee236b925..1f48be1bb12 100644 --- a/docs/SymptomsCausedByCOVIDLots/index.html +++ b/docs/SymptomsCausedByCOVIDLots/index.html @@ -13870,6 +13870,7 @@ + diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js index a59b72f1b6c..4168751077e 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartDataProvider.js @@ -16,14 +16,16 @@ class SymptomVsSymptomChartDataProvider { dict1: prrByLotX, dict2: prrByLotY }); - return Object - .keys(prrByLotXCommon) - .map( - lot => - ({ - x: prrByLotXCommon[lot], - y: prrByLotYCommon[lot] - })); + const lots = Object.keys(prrByLotXCommon); + return { + labels: lots, + data: + lots.map( + lot => ({ + x: prrByLotXCommon[lot], + y: prrByLotYCommon[lot] + })) + }; } static #getCommonKeys(dict1, dict2) { diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index b3db01ea086..493548e77a9 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -7,6 +7,7 @@ class SymptomVsSymptomChartView { this.#canvas = canvas; } + // FK-TODO: refactor displayChart(symptomX, symptomY) { if (this.#chart != null) { this.#chart.destroy(); @@ -21,8 +22,8 @@ class SymptomVsSymptomChartView { const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); const data = { datasets: [{ - label: 'Scatter Dataset', - data: chartData, + labels: chartData.labels, + data: chartData.data, backgroundColor: 'rgb(0, 0, 255)' }], }; @@ -35,6 +36,18 @@ class SymptomVsSymptomChartView { type: 'linear', position: 'bottom' } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return 'Batch: ' + context.dataset.labels[context.dataIndex]; + } + } + } } } }; diff --git a/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js b/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js index 66fa15d6cf8..bada17d4c58 100644 --- a/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js +++ b/docs/SymptomsCausedByCOVIDLots/test/SymptomVsSymptomChartDataProviderTest.js @@ -67,11 +67,14 @@ QUnit.module('SymptomVsSymptomChartDataProviderTest', function () { // Then assert.deepEqual( chartData, - [ - { - x: 2.0, - y: 3.0 - } - ]); + { + labels: ["lotCommon"], + data: [ + { + x: 2.0, + y: 3.0 + } + ] + }); }); }); \ No newline at end of file From f1262e8ab6d441029575bda446106d3380207cd5 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 09:42:50 +0100 Subject: [PATCH 07/16] refactoring --- .../js/SymptomVsSymptomChartView.js | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index 493548e77a9..ef7daf31421 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -12,56 +12,52 @@ class SymptomVsSymptomChartView { if (this.#chart != null) { this.#chart.destroy(); } - // FK-TODO: fetch multiple files: https://stackoverflow.com/a/31711496 or https://stackoverflow.com/a/53892713 - PrrByVaccineProvider.getPrrByVaccine(symptomX) - .then( - prrByLotX => { - PrrByVaccineProvider.getPrrByVaccine(symptomY) - .then( - prrByLotY => { - const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); - const data = { - datasets: [{ - labels: chartData.labels, - data: chartData.data, - backgroundColor: 'rgb(0, 0, 255)' - }], - }; - const config = { - type: 'scatter', - data: data, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - } - }, - plugins: { - legend: { - display: false - }, - tooltip: { - callbacks: { - label: function (context) { - return 'Batch: ' + context.dataset.labels[context.dataIndex]; - } - } - } - } + // FK-TODO: move PrrByVaccineProvider.getPrrByVaccine() calls out of this function + Promise + .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) + .then(([prrByLotX, prrByLotY]) => { + const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); + const data = { + datasets: [{ + labels: chartData.labels, + data: chartData.data, + backgroundColor: 'rgb(0, 0, 255)' + }], + }; + const config = { + type: 'scatter', + data: data, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom' + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return 'Batch: ' + context.dataset.labels[context.dataIndex]; } - }; - this.#chart = new Chart( - this.#canvas, - // { - // type: 'bar', - // plugins: [ChartDataLabels], - // data: this.#getData(ADRDescr), - // options: this.#getOptions() - // } - config); - }); - }); + } + } + } + } + }; + this.#chart = new Chart( + this.#canvas, + // { + // type: 'bar', + // plugins: [ChartDataLabels], + // data: this.#getData(ADRDescr), + // options: this.#getOptions() + // } + config); + }); } setData(histoDescr) { From 99b210e4fff2ad4052891d8cf0b813d0745bd683 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 10:22:20 +0100 Subject: [PATCH 08/16] labeling axes in scatter chart --- .../js/PageInitializer.js | 2 +- .../js/SymptomVsSymptomChartView.js | 88 ++++++++++--------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js index ccf650cbe39..8cb9b3f2ae1 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js +++ b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js @@ -6,7 +6,7 @@ class PageInitializer { PageInitializer.#configureSymptom(symptom); PageInitializer.#configureVaccine(vaccine); PageInitializer.#symptomVsSymptomChartView = new SymptomVsSymptomChartView(symptomVsSymptomChartViewElement); - PageInitializer.#symptomVsSymptomChartView.displayChart('Immunosuppression', 'Immunoglobulin therapy'); + PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart('Immunosuppression', 'Immunoglobulin therapy'); } static #configureSymptom({ symptomSelectElement, prrByVaccineTableElement, downloadPrrByVaccineTableButton }) { diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index ef7daf31421..8a01b11386d 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -8,56 +8,62 @@ class SymptomVsSymptomChartView { } // FK-TODO: refactor - displayChart(symptomX, symptomY) { + loadAndDisplayChart(symptomX, symptomY) { + Promise + .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) + .then(([prrByLotX, prrByLotY]) => this.#displayChart(prrByLotX, prrByLotY, symptomX, symptomY)); + } + + #displayChart(prrByLotX, prrByLotY, symptomX, symptomY) { if (this.#chart != null) { this.#chart.destroy(); } - // FK-TODO: move PrrByVaccineProvider.getPrrByVaccine() calls out of this function - Promise - .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) - .then(([prrByLotX, prrByLotY]) => { - const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); - const data = { - datasets: [{ + const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); + const data = { + datasets: + [ + { labels: chartData.labels, data: chartData.data, backgroundColor: 'rgb(0, 0, 255)' - }], - }; - const config = { - type: 'scatter', - data: data, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom' - } - }, - plugins: { - legend: { - display: false - }, - tooltip: { - callbacks: { - label: function (context) { - return 'Batch: ' + context.dataset.labels[context.dataIndex]; - } - } + } + ] + }; + const config = { + type: 'scatter', + data: data, + options: { + scales: { + x: { + type: 'linear', + position: 'bottom', + title: { + display: true, + text: 'PRR ratio of Batch for ' + symptomX + } + }, + y: { + title: { + display: true, + text: 'PRR ratio of Batch for ' + symptomY + } + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return 'Batch: ' + context.dataset.labels[context.dataIndex]; } } } - }; - this.#chart = new Chart( - this.#canvas, - // { - // type: 'bar', - // plugins: [ChartDataLabels], - // data: this.#getData(ADRDescr), - // options: this.#getOptions() - // } - config); - }); + } + } + }; + this.#chart = new Chart(this.#canvas, config); } setData(histoDescr) { From cee46f56ec05455c4df13c456ae49613e68b8993 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 10:27:46 +0100 Subject: [PATCH 09/16] refactoring --- .../js/SymptomVsSymptomChartView.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index 8a01b11386d..43b313cb3fd 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -11,10 +11,16 @@ class SymptomVsSymptomChartView { loadAndDisplayChart(symptomX, symptomY) { Promise .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) - .then(([prrByLotX, prrByLotY]) => this.#displayChart(prrByLotX, prrByLotY, symptomX, symptomY)); + .then( + ([prrByLotX, prrByLotY]) => + this.#displayChart( + { symptom: symptomX, prrByLot: prrByLotX }, + { symptom: symptomY, prrByLot: prrByLotY })); } - #displayChart(prrByLotX, prrByLotY, symptomX, symptomY) { + #displayChart( + { symptom: symptomX, prrByLot: prrByLotX }, + { symptom: symptomY, prrByLot: prrByLotY }) { if (this.#chart != null) { this.#chart.destroy(); } From a61615406b23283693c1decb02bfcd372aa10428 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 10:49:35 +0100 Subject: [PATCH 10/16] refactoring --- .../js/SymptomVsSymptomChartView.js | 140 ++++++------------ 1 file changed, 48 insertions(+), 92 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index 43b313cb3fd..a9436fe133d 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -12,64 +12,10 @@ class SymptomVsSymptomChartView { Promise .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) .then( - ([prrByLotX, prrByLotY]) => - this.#displayChart( - { symptom: symptomX, prrByLot: prrByLotX }, - { symptom: symptomY, prrByLot: prrByLotY })); - } - - #displayChart( - { symptom: symptomX, prrByLot: prrByLotX }, - { symptom: symptomY, prrByLot: prrByLotY }) { - if (this.#chart != null) { - this.#chart.destroy(); - } - const chartData = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); - const data = { - datasets: - [ - { - labels: chartData.labels, - data: chartData.data, - backgroundColor: 'rgb(0, 0, 255)' - } - ] - }; - const config = { - type: 'scatter', - data: data, - options: { - scales: { - x: { - type: 'linear', - position: 'bottom', - title: { - display: true, - text: 'PRR ratio of Batch for ' + symptomX - } - }, - y: { - title: { - display: true, - text: 'PRR ratio of Batch for ' + symptomY - } - } - }, - plugins: { - legend: { - display: false - }, - tooltip: { - callbacks: { - label: function (context) { - return 'Batch: ' + context.dataset.labels[context.dataIndex]; - } - } - } - } - } - }; - this.#chart = new Chart(this.#canvas, config); + ([prrByLotX, prrByLotY]) => { + const { labels, data } = SymptomVsSymptomChartDataProvider.getChartData({ prrByLotX, prrByLotY }); + this.#displayChart(symptomX, symptomY, labels, data); + }); } setData(histoDescr) { @@ -78,48 +24,58 @@ class SymptomVsSymptomChartView { this.#chart.update(); } - #getData(ADRDescr) { + #displayChart(symptomX, symptomY, labels, data) { + if (this.#chart != null) { + this.#chart.destroy(); + } + const config = { + type: 'scatter', + data: this.#getData(labels, data), + options: this.#getOptions(symptomX, symptomY) + }; + this.#chart = new Chart(this.#canvas, config); + } + + #getData(labels, data) { return { - labels: [ - 'Deaths', - 'Disabilities', - 'Life Threatening Illnesses', - 'Other Adverse Events' - ], - datasets: [{ - // FK-TODO: refactor - label: 'Batch ' + ADRDescr['batchcode'], - data: [ - ADRDescr['Deaths'], - ADRDescr['Disabilities'], - ADRDescr['Life Threatening Illnesses'], - ADRDescr['Adverse Reaction Reports'] - (ADRDescr['Deaths'] + ADRDescr['Disabilities'] + ADRDescr['Life Threatening Illnesses']) - ], - backgroundColor: '#1a73e8' - }] + datasets: + [ + { + labels: labels, + data: data, + backgroundColor: 'rgb(0, 0, 255)' + } + ] }; } - #getOptions() { + #getOptions(symptomX, symptomY) { return { - plugins: { - datalabels: { - anchor: 'end', - align: 'top' - } - }, - title: { - display: true, - position: 'top' - }, scales: { - y: { - ticks: { - precision: 0 - }, + x: { + type: 'linear', + position: 'bottom', title: { display: true, - text: 'Frequency' + text: 'PRR ratio of Batch for ' + symptomX + } + }, + y: { + title: { + display: true, + text: 'PRR ratio of Batch for ' + symptomY + } + } + }, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return 'Batch: ' + context.dataset.labels[context.dataIndex]; + } } } } From 15786071c80a6cb87217a8eb241efc85d50dca38 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 10:55:16 +0100 Subject: [PATCH 11/16] refactoring --- .../js/SymptomVsSymptomChartView.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index a9436fe133d..c9aa6443daa 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -28,12 +28,18 @@ class SymptomVsSymptomChartView { if (this.#chart != null) { this.#chart.destroy(); } - const config = { + this.#chart = + new Chart( + this.#canvas, + this.#getConfig(symptomX, symptomY, labels, data)); + } + + #getConfig(symptomX, symptomY, labels, data) { + return { type: 'scatter', data: this.#getData(labels, data), options: this.#getOptions(symptomX, symptomY) }; - this.#chart = new Chart(this.#canvas, config); } #getData(labels, data) { @@ -55,16 +61,10 @@ class SymptomVsSymptomChartView { x: { type: 'linear', position: 'bottom', - title: { - display: true, - text: 'PRR ratio of Batch for ' + symptomX - } + title: this.#getAxisTitle(symptomX) }, y: { - title: { - display: true, - text: 'PRR ratio of Batch for ' + symptomY - } + title: this.#getAxisTitle(symptomY) } }, plugins: { @@ -81,4 +81,11 @@ class SymptomVsSymptomChartView { } }; } + + #getAxisTitle(symptom) { + return { + display: true, + text: 'PRR ratio of Batch for ' + symptom + } + } } \ No newline at end of file From eb69b5d55315997523829d67345caefda3034e68 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 11:29:38 +0100 Subject: [PATCH 12/16] making symptoms for scatter chart axes selectable --- docs/SymptomsCausedByCOVIDLots/index.html | 26358 +++++++++++++++- .../js/PageInitializer.js | 30 +- .../js/SymptomVsSymptomChartView.js | 7 - 3 files changed, 26383 insertions(+), 12 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/index.html b/docs/SymptomsCausedByCOVIDLots/index.html index 1f48be1bb12..5b2015cb1f9 100644 --- a/docs/SymptomsCausedByCOVIDLots/index.html +++ b/docs/SymptomsCausedByCOVIDLots/index.html @@ -54,7 +54,11 @@ prrBySymptomTableElement: $('#prrBySymptomTable'), downloadPrrBySymptomTableButton: document.querySelector("#downloadPrrBySymptomTable") }, - symptomVsSymptomChartViewElement: document.querySelector('#symptomVsSymptomChartView') + symptomVsSymptomChart: { + symptomSelectXElement: $('#symptomSelectX'), + symptomSelectYElement: $('#symptomSelectY'), + symptomVsSymptomChartViewElement: document.querySelector('#symptomVsSymptomChartView') + } } ); }); @@ -13831,10 +13835,26360 @@
-

Worst Batches Bla Bla

+

Comparison of the PRRs of two Symptoms

+
+ + +
+
+ + +
diff --git a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js index 8cb9b3f2ae1..a9c5eca986e 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js +++ b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js @@ -1,12 +1,13 @@ class PageInitializer { static #symptomVsSymptomChartView; + static #symptomX = 'Immunosuppression'; + static #symptomY = 'Immunoglobulin therapy'; - static initializePage({ symptom, vaccine, symptomVsSymptomChartViewElement }) { + static initializePage({ symptom, vaccine, symptomVsSymptomChart }) { PageInitializer.#configureSymptom(symptom); PageInitializer.#configureVaccine(vaccine); - PageInitializer.#symptomVsSymptomChartView = new SymptomVsSymptomChartView(symptomVsSymptomChartViewElement); - PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart('Immunosuppression', 'Immunoglobulin therapy'); + PageInitializer.#configureSymptomVsSymptomChart(symptomVsSymptomChart); } static #configureSymptom({ symptomSelectElement, prrByVaccineTableElement, downloadPrrByVaccineTableButton }) { @@ -29,6 +30,29 @@ class PageInitializer { }); } + static #configureSymptomVsSymptomChart({ symptomSelectXElement, symptomSelectYElement, symptomVsSymptomChartViewElement }) { + PageInitializer.#symptomVsSymptomChartView = new SymptomVsSymptomChartView(symptomVsSymptomChartViewElement); + PageInitializer.#initializeSelectElement( + { + selectElement: symptomSelectXElement, + onValueSelected: symptomX => { + PageInitializer.#symptomX = symptomX; + PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); + }, + minimumInputLength: 0 + }); + PageInitializer.#initializeSelectElement( + { + selectElement: symptomSelectYElement, + onValueSelected: symptomY => { + PageInitializer.#symptomY = symptomY; + PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); + }, + minimumInputLength: 0 + }); + PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); + } + static #initializeSelectElement({ selectElement, onValueSelected, minimumInputLength }) { selectElement.select2({ minimumInputLength: minimumInputLength }); selectElement.on( diff --git a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js index c9aa6443daa..e9a837563a3 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js +++ b/docs/SymptomsCausedByCOVIDLots/js/SymptomVsSymptomChartView.js @@ -7,7 +7,6 @@ class SymptomVsSymptomChartView { this.#canvas = canvas; } - // FK-TODO: refactor loadAndDisplayChart(symptomX, symptomY) { Promise .all([symptomX, symptomY].map(symptom => PrrByVaccineProvider.getPrrByVaccine(symptom))) @@ -18,12 +17,6 @@ class SymptomVsSymptomChartView { }); } - setData(histoDescr) { - const data = this.#getData(histoDescr); - this.#chart.config.data = data; - this.#chart.update(); - } - #displayChart(symptomX, symptomY, labels, data) { if (this.#chart != null) { this.#chart.destroy(); From be62c55b2d8a608cd4acee7a1bc678dc15252d05 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 11:49:58 +0100 Subject: [PATCH 13/16] generating select options in python --- .../js/PageInitializer.js | 6 +- docs/SymptomsCausedByVaccines/index.html | 17765 +--------------- src/HowBadIsMyBatch.ipynb | 9 +- src/SymptomsCausedByVaccines/HtmlUpdater.py | 13 + 4 files changed, 150 insertions(+), 17643 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js index a9c5eca986e..1151815f971 100644 --- a/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js +++ b/docs/SymptomsCausedByCOVIDLots/js/PageInitializer.js @@ -26,7 +26,7 @@ class PageInitializer { { selectElement: vaccineSelectElement, onValueSelected: vaccine => prrBySymptomTableView.displayPrrBySymptomTable4Vaccine(vaccine), - minimumInputLength: 0 + minimumInputLength: 4 }); } @@ -39,7 +39,7 @@ class PageInitializer { PageInitializer.#symptomX = symptomX; PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); }, - minimumInputLength: 0 + minimumInputLength: 4 }); PageInitializer.#initializeSelectElement( { @@ -48,7 +48,7 @@ class PageInitializer { PageInitializer.#symptomY = symptomY; PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); }, - minimumInputLength: 0 + minimumInputLength: 4 }); PageInitializer.#symptomVsSymptomChartView.loadAndDisplayChart(PageInitializer.#symptomX, PageInitializer.#symptomY); } diff --git a/docs/SymptomsCausedByVaccines/index.html b/docs/SymptomsCausedByVaccines/index.html index 2f9f2fceaca..4daf9de4990 100644 --- a/docs/SymptomsCausedByVaccines/index.html +++ b/docs/SymptomsCausedByVaccines/index.html @@ -1,41 +1,40 @@ - - - - - - - Safety Signal - - - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - -
-
- -
-
- - -
-
-
-
-

Safety Signal

- +
+
+
+
+
+

Worst Vaccines

+
+
+
+
+ + +
+ + + + + + + +
VaccineProportional Reporting Ratio
+ +
+
+
+
+
+
+

Strongest Symptoms

+
+
+
+
+ + +
+ + + + + + + +
SymptomProportional Reporting Ratio > 1
+ +
+
+
+
+
+
+
+
+ + + -
- - - - - - - - - - - - - - + +
+ +
+ + + + + + + + + + + + + + - \ No newline at end of file diff --git a/src/HowBadIsMyBatch.ipynb b/src/HowBadIsMyBatch.ipynb index f605ef6f23a..f36c8575313 100644 --- a/src/HowBadIsMyBatch.ipynb +++ b/src/HowBadIsMyBatch.ipynb @@ -438,7 +438,7 @@ "metadata": {}, "outputs": [], "source": [ - "from SymptomsCausedByVaccines.HtmlUpdater import updateHtmlFile\n", + "from SymptomsCausedByVaccines.HtmlUpdater import updateHtmlFile, updateHtmlFile4SymptomsCausedByCOVIDLots\n", "from SymptomsCausedByVaccines.PrrSeriesFactory import PrrSeriesFactory\n", "from SymptomsCausedByVaccines.PrrSeriesTransformer import PrrSeriesTransformer\n", "from SymptomsCausedByVaccines.ProportionalReportingRatiosPersister import saveProportionalReportingRatios\n", @@ -627,11 +627,10 @@ "metadata": {}, "outputs": [], "source": [ - "updateHtmlFile(\n", + "updateHtmlFile4SymptomsCausedByCOVIDLots(\n", " symptoms = list(prrByLotAndSymptom.columns),\n", - " vaccines = list(prrByLotAndSymptom.index),\n", - " htmlFile = \"../docs/SymptomsCausedByCOVIDLots/index.html\",\n", - " defaultSelectVaccineOptionText = 'Select Batch')" + " batches = list(prrByLotAndSymptom.index),\n", + " htmlFile = \"../docs/SymptomsCausedByCOVIDLots/index.html\")" ] }, { diff --git a/src/SymptomsCausedByVaccines/HtmlUpdater.py b/src/SymptomsCausedByVaccines/HtmlUpdater.py index 6ce0da9a17b..1747a207aaf 100644 --- a/src/SymptomsCausedByVaccines/HtmlUpdater.py +++ b/src/SymptomsCausedByVaccines/HtmlUpdater.py @@ -16,6 +16,19 @@ def updateHtmlFile(symptoms, vaccines, htmlFile, defaultSelectVaccineOptionText htmlFile = htmlFile, selectElementId = 'vaccineSelect') +def updateHtmlFile4SymptomsCausedByCOVIDLots(symptoms, batches, htmlFile): + symptomOptions = getSymptomOptions(symptoms) + for selectElementId in ['symptomSelect', 'symptomSelectX', 'symptomSelectY']: + _saveOptions( + options = symptomOptions, + htmlFile = htmlFile, + selectElementId = selectElementId) + + _saveOptions( + options = getVaccineOptions(batches, 'Select Batch'), + htmlFile = htmlFile, + selectElementId = 'vaccineSelect') + def _saveOptions(options, htmlFile, selectElementId): HtmlTransformerUtil().applySoupTransformerToFile( file=htmlFile, From 6f20261d23c5a5bb83f2d74c24f8cba1008cae97 Mon Sep 17 00:00:00 2001 From: frankknoll Date: Thu, 9 Nov 2023 12:00:39 +0100 Subject: [PATCH 14/16] refactoring --- docs/SymptomsCausedByCOVIDLots/index.html | 6 +++--- .../js/PageInitializer.js | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/SymptomsCausedByCOVIDLots/index.html b/docs/SymptomsCausedByCOVIDLots/index.html index 5b2015cb1f9..51e5de54172 100644 --- a/docs/SymptomsCausedByCOVIDLots/index.html +++ b/docs/SymptomsCausedByCOVIDLots/index.html @@ -13835,12 +13835,12 @@
-

Comparison of the PRRs of two Symptoms

+

Comparison of PRRs of two Symptoms

- +
- +