All files / src/shared/lib/scripts scriptHelpers.ts

86.27% Statements 44/51
51.52% Branches 17/33
75% Functions 6/8
86.96% Lines 40/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92            41x 41x 41x   41x             41x             23x   23x 23x 10x 10x   23x 10x 10x       23x       23x 10x 10x 10x     10x 3x       20x     41x 42x 42x 53x   42x       41x       10x 10x 12x 12x 12x 12x   12x   10x 3x   2x   2x 2x           7x    
// Helpers around script data
 
import {
  ScriptEventNormalized,
  ScriptEvent,
} from "shared/lib/entities/entitiesTypes";
import { walkNormalizedScript, walkScript } from "shared/lib/scripts/walk";
import isEqual from "lodash/isEqual";
import SparkMD5 from "spark-md5";
 
export const isEmptyScript = (script: ScriptEvent[]) => {
  Iif (script.length === 0) {
    return true;
  }
  return script.every((scriptEvent) => scriptEvent?.args?.__comment);
};
 
export const isNormalizedScriptEqual = (
  idsA: string[] = [],
  lookupA: Record<string, ScriptEventNormalized>,
  idsB: string[] = [],
  lookupB: Record<string, ScriptEventNormalized>
) => {
  const scriptAEvents: { args?: Record<string, unknown>; command: string }[] =
    [];
  const scriptBEvents: { args?: Record<string, unknown>; command: string }[] =
    [];
  walkNormalizedScript(idsA, lookupA, undefined, (scriptEvent) => {
    const { args, command } = scriptEvent;
    scriptAEvents.push({ args, command });
  });
  walkNormalizedScript(idsB, lookupB, undefined, (scriptEvent) => {
    const { args, command } = scriptEvent;
    scriptBEvents.push({ args, command });
  });
 
  // Exit early if script lengths differ
  Iif (scriptAEvents.length !== scriptBEvents.length) {
    return false;
  }
  // Otherwise check that every script event is equivalent
  for (let i = 0; i < scriptAEvents.length; i++) {
    const scriptEventA = scriptAEvents[i];
    const scriptEventB = scriptBEvents[i];
    Iif (scriptEventA.command !== scriptEventB.command) {
      return false;
    }
    if (!isArgsEqual(scriptEventA.args ?? {}, scriptEventB.args ?? {})) {
      return false;
    }
  }
 
  return true;
};
 
export const generateScriptHash = (script: ScriptEvent[]): string => {
  const data: unknown[] = [];
  walkScript(script, undefined, ({ args, command }) => {
    data.push({ args, command });
  });
  return SparkMD5.hash(JSON.stringify(data));
};
 
// Compare args with undefined and missing args as equivalent
const isArgsEqual = (
  a: Record<string, unknown>,
  b: Record<string, unknown>
): boolean => {
  const keys = new Set<string>([...Object.keys(a), ...Object.keys(b)]);
  for (const key of keys) {
    const hasKeyA = Object.prototype.hasOwnProperty.call(a, key);
    const hasKeyB = Object.prototype.hasOwnProperty.call(b, key);
    const valA = a[key];
    const valB = b[key];
 
    if (hasKeyA && hasKeyB) {
      // Both objects have the key
      if (!isEqual(valA, valB)) {
        return false;
      }
    } else if (hasKeyA || hasKeyB) {
      // One object has the key; check if its value is undefined
      const val = hasKeyA ? valA : valB;
      Iif (val !== undefined) {
        // Values are different since one is not undefined
        return false;
      }
    }
  }
  return true;
};