core/converters/viewport.converter.ts

core/converters/viewport.converter.ts is a file in Project Scripts. 81 lines of code and 17 definitions.

import {
    HOME_PAGE,
    HOME_ROUTE,
    HOME_SHOT,
    MISSING_PAGE,
    PAGE_EXTENSION,
    ROUTE_SEPARATOR,
    SHOT_EXTENSION,
    SHOT_SEPARATOR,
} from "#configuration/constants/viewport.constants";
import type { ViewportAudit, ViewportOptions } from "#types/viewport.types";
import { flagValue, flagValues, hasFlag, numberFlag } from "@govlab/argv";
import { resolve, sep } from "node:path";
import type { ParsedArgv } from "@govlab/argv";
import { VIEWPORT_DEFAULTS } from "#configuration/configs/viewport.config";

export const readOptions = function readOptions(argv: ParsedArgv): ViewportOptions | null {
    const outDir = flagValue(argv, "--out-dir");
    if (outDir === undefined) {
        return null;
    }
    return {
        browser: flagValue(argv, "--browser") ?? null,
        height: numberFlag(argv, "--height", VIEWPORT_DEFAULTS.height),
        outDir: resolve(outDir),
        routes: flagValues(argv, "--route"),
        settleMs: numberFlag(argv, "--settle", VIEWPORT_DEFAULTS.settleMs),
        software: !hasFlag(argv, "--gpu"),
        timeoutMs: numberFlag(argv, "--timeout", VIEWPORT_DEFAULTS.timeoutMs),
        width: numberFlag(argv, "--width", VIEWPORT_DEFAULTS.width),
    };
};

export const routeOfPage = function routeOfPage(page: string): string | null {
    const path = page.split(sep).join(ROUTE_SEPARATOR);
    if (!path.endsWith(PAGE_EXTENSION) || path.endsWith(MISSING_PAGE)) {
        return null;
    }
    return path === HOME_PAGE ? HOME_ROUTE : ROUTE_SEPARATOR + path.slice(0, -PAGE_EXTENSION.length);
};

export const shotNameOf = function shotNameOf(route: string): string {
    const name =
        route === HOME_ROUTE
            ? HOME_SHOT
            : route.slice(ROUTE_SEPARATOR.length).split(ROUTE_SEPARATOR).join(SHOT_SEPARATOR);
    return name + SHOT_EXTENSION;
};

const FAILING_LISTS = ["overflow", "inputs", "text", "targets"] as const;

const isRecord = function isRecord(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null;
};

const textsAt = function textsAt(record: Record<string, unknown>, key: string): readonly string[] | null {
    const value = record[key];
    return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : null;
};

export const auditOf = function auditOf(value: unknown): ViewportAudit | null {
    if (!isRecord(value) || typeof value["viewport"] !== "number" || typeof value["scrollsSideways"] !== "boolean") {
        return null;
    }
    const clipped = textsAt(value, "clipped");
    const inputs = textsAt(value, "inputs");
    const overflow = textsAt(value, "overflow");
    const targets = textsAt(value, "targets");
    const text = textsAt(value, "text");
    if (clipped === null || inputs === null || overflow === null) {
        return null;
    }
    if (targets === null || text === null) {
        return null;
    }
    return {
        clipped,
        inputs,
        overflow,
        scrollsSideways: value["scrollsSideways"],
        targets,
        text,
        viewport: value["viewport"],
    };
};

export const hasFinding = function hasFinding(audit: ViewportAudit): boolean {
    return audit.scrollsSideways || FAILING_LISTS.some((key) => audit[key].length > 0);
};