# core/converters/viewport.converter.ts

> 81 lines of code and 17 definitions.

Tree: Project Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/scripts#file-scripts-core-converters-viewport-converter-ts
Source text: https://banes-lab.com/source/scripts/core/converters/viewport.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/scripts/core/converters.md), after [core/converters/snapshot.converter.ts](https://banes-lab.com/source/scripts/core/converters/snapshot.converter.ts.md).

## Definitions

- `isRecord` (lexical_declaration, line 52)
- `routeOfPage` (lexical_declaration, line 34, exported)
- `shotNameOf` (lexical_declaration, line 42, exported)
- `auditOf` (lexical_declaration, line 61, exported)
- `readOptions` (lexical_declaration, line 17, exported)
- `outDir` (lexical_declaration, line 18, exported)
- `path` (lexical_declaration, line 35, exported)
- `name` (lexical_declaration, line 43, exported)
- `FAILING_LISTS` (lexical_declaration, line 50)
- `textsAt` (lexical_declaration, line 56)
- `value` (lexical_declaration, line 57)
- `clipped` (lexical_declaration, line 65, exported)
- `inputs` (lexical_declaration, line 66, exported)
- `overflow` (lexical_declaration, line 67, exported)
- `targets` (lexical_declaration, line 68, exported)
- `text` (lexical_declaration, line 69, exported)
- `hasFinding` (lexical_declaration, line 87, exported)

## Contained in

- [core/converters](https://banes-lab.com/anatomy/scripts/folder-scripts-core-converters.md)

## Used by

- [core/coordinators/viewport.coordinator.ts](https://banes-lab.com/source/scripts/core/coordinators/viewport.coordinator.ts.md)
- [core/loaders/viewport.loader.ts](https://banes-lab.com/source/scripts/core/loaders/viewport.loader.ts.md)
- [core/selectors/manifest.selector.ts](https://banes-lab.com/source/scripts/core/selectors/manifest.selector.ts.md)
- [core/selectors/rate.selector.ts](https://banes-lab.com/source/scripts/core/selectors/rate.selector.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/scripts/folder-scripts-core-converters.md)
- [core/coordinators](https://banes-lab.com/anatomy/scripts/folder-scripts-core-coordinators.md)
- [core/loaders](https://banes-lab.com/anatomy/scripts/folder-scripts-core-loaders.md)
- [core/selectors](https://banes-lab.com/anatomy/scripts/folder-scripts-core-selectors.md)

## Source

```typescript
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);
};
```
