tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

numeric-testcommon.js (8544B)


      1 'use strict';
      2 
      3 /*
      4 Provides functions to help test that two numeric values are equivalent.
      5 These *do not* rely on you predicting what one value will serialize to;
      6 instead, they set and serialize *both* values,
      7 and just ensure that they serialize to the same thing.
      8 
      9 They rely on a #target element existing in the document,
     10 as this might rely on layout to resolve styles,
     11 and so it needs to be in the document.
     12 
     13 Three main functions are defined, with the same signatures:
     14 test_math_used() (for testing used values),
     15 test_math_computed() (for testing computed values),
     16 and test_math_specified() (for testing specified values).
     17 Signature for all is:
     18 
     19 test_math_X(
     20    testString, // A numeric value; required.
     21    expectedString, // A hopefully-equivalent numeric value; required.
     22    { // all of these are optional
     23        type, // "number", "length", etc. See impl for full list. Defaults to "length".
     24        msg, // The message to display for the test; autogenned if not provided.
     25        msgExtra, // Extra info to put after the auto-genned message.
     26        prop, // If you want to override the automatic choice of tested property.
     27        extraStyle, // Styles that need to be set at the same time to properly test the value.
     28        approx, // The epsilon in order to compare numeric-ish values.
     29                // Note that it'd use parseFloat in order to extract the
     30                // values, so they can drop units or what not.
     31    }
     32 );
     33 
     34 
     35 Additionally, five specialized functions are provided
     36 to test that a given value is ±∞, ±0, or NaN:
     37 
     38 * test_plus_infinity(testString)
     39 * test_minus_infinity(testString)
     40 * test_plus_zero(testString)
     41 * test_minus_zero(testString)
     42 * test_nan(testString)
     43 
     44 */
     45 
     46 
     47 
     48 function test_math_used(testString, expectedString, {approx, msg, msgExtra, type, prop, extraStyle={}}={}) {
     49    if(type === undefined) type = "length";
     50    if(!prop) {
     51        switch(type) {
     52            case "number":     prop = "scale"; break;
     53            case "integer":    prop = "z-index"; extraStyle.position="absolute"; break;
     54            case "length":     prop = "margin-left"; break;
     55            case "angle":      prop = "rotate"; break;
     56            case "time":       prop = "transition-delay"; break;
     57            case "resolution": prop = "image-resolution"; break;
     58            case "flex":       prop = "grid-template-rows"; break;
     59            default: throw Exception(`Value type '${type}' isn't capable of math.`);
     60        }
     61 
     62    }
     63    _test_math({stage:'used', testString, expectedString, type, approx, msg, msgExtra, prop, extraStyle});
     64 }
     65 
     66 function test_math_computed(testString, expectedString, {approx, msg, msgExtra, type, prop, extraStyle={}}={}) {
     67    if(type === undefined) type = "length";
     68    if(!prop) {
     69        switch(type) {
     70            case "number":     prop = "scale"; break;
     71            case "integer":    prop = "z-index"; extraStyle.position="absolute"; break;
     72            case "length":     prop = "flex-basis"; break;
     73            case "angle":      prop = "rotate"; break;
     74            case "time":       prop = "transition-delay"; break;
     75            case "resolution": prop = "image-resolution"; break;
     76            case "flex":       prop = "grid-template-rows"; break;
     77            default: throw Exception(`Value type '${type}' isn't capable of math.`);
     78        }
     79 
     80    }
     81    _test_math({stage:'computed', testString, expectedString, type, approx, msg, msgExtra, prop, extraStyle});
     82 }
     83 
     84 function test_math_specified(testString, expectedString, {approx, msg, msgExtra, type, prop, extraStyle={}}={}) {
     85    if(type === undefined) type = "length";
     86    const stage = "specified";
     87    if(!prop) {
     88        switch(type) {
     89            case "number":     prop = "scale"; break;
     90            case "integer":    prop = "z-index"; extraStyle.position="absolute"; break;
     91            case "length":     prop = "flex-basis"; break;
     92            case "angle":      prop = "rotate"; break;
     93            case "time":       prop = "transition-delay"; break;
     94            case "resolution": prop = "image-resolution"; break;
     95            case "flex":       prop = "grid-template-rows"; break;
     96            default: throw Exception(`Value type '${type}' isn't capable of math.`);
     97        }
     98 
     99    }
    100    // Find the test element
    101    const testEl = document.getElementById('target');
    102    if(testEl == null) throw "Couldn't find #target element to run tests on.";
    103    // Then reset its styles
    104    testEl.style = "";
    105    for(const p in extraStyle) {
    106        testEl.style[p] = extraStyle[p];
    107    }
    108    if(!msg) {
    109        msg = `${testString} should be ${stage}-value-equivalent to ${expectedString}`;
    110        if(msgExtra) msg += "; " + msgExtra;
    111    }
    112    let t = testString;
    113    let e = expectedString;
    114    test(()=>{
    115        testEl.style[prop] = '';
    116        testEl.style[prop] = t;
    117        const usedValue = testEl.style[prop];
    118        assert_not_equals(usedValue, '', `${testString} isn't valid in '${prop}'; got the default value instead.`);
    119        testEl.style[prop] = '';
    120        testEl.style[prop] = e;
    121        const expectedValue = testEl.style[prop];
    122        assert_not_equals(expectedValue, '', `${expectedString} isn't valid in '${prop}'; got the default value instead.`)
    123        if (approx) {
    124            let extractValue = function(value) {
    125                if (value.startsWith("calc(")) {
    126                    value = value.slice("calc(".length, -1);
    127                }
    128                return parseFloat(value);
    129            };
    130            let parsedUsed = extractValue(usedValue);
    131            let parsedExpected = extractValue(expectedValue);
    132            assert_approx_equals(parsedUsed, parsedExpected, approx, `${testString} and ${expectedString} ${approx} serialize to the same thing in ${stage} values.`);
    133        } else {
    134            assert_equals(usedValue, expectedValue, `${testString} and ${expectedString} serialize to the same thing in ${stage} values.`);
    135        }
    136    }, msg);
    137 }
    138 
    139 /*
    140 All of these expect the testString to evaluate to a <number>.
    141 */
    142 function test_plus_infinity(testString) {
    143    test_math_used(testString, "calc(infinity)", {type:"number"});
    144 }
    145 function test_minus_infinity(testString) {
    146    test_math_used(testString, "calc(-infinity)", {type:"number"});
    147 }
    148 function test_plus_zero(testString) {
    149    test_math_used(`calc(1 / ${testString})`, "calc(infinity)", {type:"number"});
    150 }
    151 function test_minus_zero(testString) {
    152    test_math_used(`calc(1 / ${testString})`, "calc(-infinity)", {type:"number"});
    153 }
    154 function test_nan(testString) {
    155    // Make sure that it's NaN, not an infinity,
    156    // by making sure that it's the same value both pos and neg.
    157    test_math_used(testString, "calc(NaN)", {type:"number"});
    158    test_math_used(`calc(-1 * ${testString})`, "calc(NaN)", {type:"number"});
    159 }
    160 
    161 
    162 function _test_math({stage, testEl, testString, expectedString, type, approx, msg, msgExtra, prop, extraStyle}={}) {
    163    // Find the test element
    164    if(!testEl) testEl = document.getElementById('target');
    165    if(testEl == null) throw "Couldn't find #target element to run tests on.";
    166    // Then reset its styles
    167    testEl.style = "";
    168    for(const p in extraStyle) {
    169        testEl.style[p] = extraStyle[p];
    170    }
    171    if(!msg) {
    172        msg = `${testString} should be ${stage}-value-equivalent to ${expectedString}`;
    173        if(msgExtra) msg += "; " + msgExtra;
    174    }
    175    let t = testString;
    176    let e = expectedString;
    177    test(()=>{
    178        testEl.style[prop] = '';
    179        const defaultValue = getComputedStyle(testEl)[prop];
    180        testEl.style[prop] = t;
    181        const usedValue = getComputedStyle(testEl)[prop];
    182        assert_not_equals(usedValue, defaultValue, `${testString} isn't valid in '${prop}'; got the default value instead.`);
    183        testEl.style[prop] = '';
    184        testEl.style[prop] = e;
    185        const expectedValue = getComputedStyle(testEl)[prop];
    186        assert_not_equals(expectedValue, defaultValue, `${expectedString} isn't valid in '${prop}'; got the default value instead.`)
    187        if (approx) {
    188            let extractValue = function(value) {
    189                return parseFloat(value);
    190            };
    191            let parsedUsed = extractValue(usedValue);
    192            let parsedExpected = extractValue(expectedValue);
    193            assert_approx_equals(parsedUsed, parsedExpected, approx, `${testString} and ${expectedString} ${approx} serialize to the same thing in ${stage} values.`);
    194        } else {
    195            assert_equals(usedValue, expectedValue, `${testString} and ${expectedString} serialize to the same thing in ${stage} values.`);
    196        }
    197    }, msg);
    198 }