util.js (6025B)
1 function is_same_sanitizer_name(a, b) { 2 return a.name === b.name && a.namespace === b.namespace; 3 } 4 5 // https://pr-preview.s3.amazonaws.com/otherdaniel/purification/pull/296.html#sanitizerconfig-valid 6 function assert_config_is_valid(config) { 7 // The config has either an elements or a removeElements key, but not both. 8 assert_false( 9 "elements" in config && "removeElements" in config, 10 "Either elements or a removeElements, but not both", 11 ); 12 assert_true( 13 "elements" in config || "removeElements" in config, 14 "Either elements or a removeElements", 15 ); 16 17 // The config has either an attributes or a removeAttributes key, but not both. 18 assert_false( 19 "attributes" in config && "removeAttributes" in config, 20 "Either attributes or a removeAttributes, but not both", 21 ); 22 assert_true( 23 "attributes" in config || "removeAttributes" in config, 24 "Either attributes or removeAttributes", 25 ); 26 27 // If both config[elements] and config[replaceWithChildrenElements] exist, then the difference of config[elements] and config[replaceWithChildrenElements] is empty. 28 if (config.elements && config.replaceWithChildrenElements) { 29 for (let element of config.elements) { 30 assert_false( 31 config.replaceWithChildrenElements.some((replaceElement) => 32 is_same_sanitizer_name(element, replaceElement), 33 ), 34 `replaceWithChildrenElements should not contain ${element.name}`, 35 ); 36 } 37 } 38 39 // If both config[removeElements] and config[replaceWithChildrenElements] exist, then the difference of config[removeElements] and config[replaceWithChildrenElements] is empty. 40 if (config.removeElements && config.replaceWithChildrenElements) { 41 for (let removeElement of config.removeElements) { 42 assert_false( 43 config.replaceWithChildrenElements.some((replaceElement) => 44 is_same_sanitizer_name(removeElement, replaceElement), 45 ), 46 `replaceWithChildrenElements should not contain ${removeElement.name}`, 47 ); 48 } 49 } 50 51 // If config[attributes] exists: 52 if (config.attributes) { 53 } else { 54 if (config.elements) { 55 for (let element of config.elements) { 56 // Not both element[attributes] and element[removeAttributes] exist. 57 assert_false("attributes" in element && "removeAttributes" in element, 58 `Element ${element.name} can't have both 'attributes' and 'removeAttributes'`); 59 } 60 } 61 62 // config[dataAttributes] does not exist. 63 assert_false("dataAttributes" in config, "dataAttributes does not exist"); 64 } 65 } 66 67 function assert_config(config, expected) { 68 const PROPERTIES = [ 69 "attributes", 70 "removeAttributes", 71 "elements", 72 "removeElements", 73 "replaceWithChildrenElements", 74 "comments", 75 "dataAttributes", 76 ]; 77 78 // Prevent some typos in the expected config. 79 for (let key of Object.keys(expected)) { 80 assert_in_array(key, PROPERTIES, "expected"); 81 } 82 for (let key of Object.keys(config)) { 83 assert_in_array(key, PROPERTIES, "config"); 84 } 85 86 assert_config_is_valid(config); 87 88 // XXX duplications 89 // XXX other consistency checks 90 91 function assert_attrs(key, config, expected, prefix = "config") { 92 // XXX we allow specifying only a subset for expected. 93 if (!(key in expected)) { 94 return; 95 } 96 97 if (expected[key] === undefined) { 98 assert_false(key in config, `Unexpected '${key}' in ${prefix}`); 99 return; 100 } 101 102 assert_true(key in config, `Missing '${key}' from ${prefix}`); 103 assert_equals(config[key]?.length, expected[key].length, `${prefix}.${key}.length`); 104 for (let i = 0; i < expected[key].length; i++) { 105 let attribute = expected[key][i]; 106 if (typeof attribute === "string") { 107 assert_object_equals( 108 config[key][i], 109 { name: attribute, namespace: null }, 110 `${prefix}.${key}[${i}] should match`, 111 ); 112 } else { 113 assert_object_equals( 114 config[key][i], 115 attribute, 116 `${prefix}.${key}[${i}] should match`, 117 ); 118 } 119 } 120 } 121 122 assert_attrs("attributes", config, expected); 123 assert_attrs("removeAttributes", config, expected); 124 125 function assert_elems(key) { 126 if (!(key in expected)) { 127 return; 128 } 129 130 if (expected[key] === undefined) { 131 assert_false(key in config, `Unexpected '${key}' in config`); 132 return; 133 } 134 135 assert_true(key in config, `Missing '${key}' from config`); 136 assert_equals(config[key]?.length, expected[key].length, `${key}.length`); 137 138 const XHTML_NS = "http://www.w3.org/1999/xhtml"; 139 140 for (let i = 0; i < expected[key].length; i++) { 141 let element = expected[key][i]; 142 // To make writing tests a bit easier we also support the shorthand string syntax. 143 if (typeof element === "string") { 144 let extra = key === "elements" ? { removeAttributes: [] } : { }; 145 assert_object_equals( 146 config[key][i], 147 { name: element, namespace: XHTML_NS, ...extra }, 148 `${key}[${i}] should match`, 149 ); 150 } else { 151 if (key === "elements") { 152 assert_equals(config[key][i].name, element.name, `${key}[${i}].name should match`); 153 let ns = "namespace" in element ? element.namespace : XHTML_NS; 154 assert_equals(config[key][i].namespace, ns, `${key}[${i}].namespace should match`); 155 156 assert_attrs("attributes", config[key][i], element, `config.elements[${i}]`); 157 assert_attrs("removeAttributes", config[key][i], element, `config.elements[${i}]`); 158 } else { 159 assert_object_equals(config[key][i], element, `${key}[${i}] should match`); 160 } 161 } 162 } 163 } 164 165 assert_elems("elements"); 166 assert_elems("removeElements"); 167 assert_elems("replaceWithChildrenElements"); 168 169 if ("comments" in expected) { 170 assert_equals(config.comments, expected.comments, "comments should match"); 171 } 172 173 if ("dataAttributes" in expected) { 174 assert_equals(config.dataAttributes, expected.dataAttributes, "dataAttributes should match"); 175 } 176 }