Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue testing type guards with generics #8

Closed
Goldziher opened this issue Aug 4, 2021 · 3 comments
Closed

Issue testing type guards with generics #8

Goldziher opened this issue Aug 4, 2021 · 3 comments

Comments

@Goldziher
Copy link

Hi there,

First of - thanks for this library. Its very helpful.

I am having issues testing type guards that use generics. For example, I have a guard that looks like this

const createGuard = <T>(
    validator: (value: unknown) => boolean,
    label: string,
) => {
    return (input: unknown, throwError = false): input is T => {
        if (!validator(input)) {
            if (throwError) {
                throw new TypeError(`expected input to be ${label}`);
            }
            return false;
        }
        return true;
    };
};

export function isArray<T>(
    input: unknown,
    valueGuard: (value: unknown) => boolean,
    throwError = false,
): input is T[] {
    return createGuard<T[]>(
        (value) => Array.isArray(value) && value.every(valueGuard),
        'Array',
    )(input, throwError);
}

I then test it like so:

describe('isArray', () => {
    it('returns true for positively tested array values', () => {
        expect(isArray<string>(stringArray, isString)).toBeTruthy();
        expect(isArray<number>(numberArray, isNumber)).toBeTruthy();
        expect(isArray<symbol>(symbolArray, isSymbol)).toBeTruthy();
        expect(isArray<object>(recordArray, isObject)).toBeTruthy();
        expect(
            isArray<string | number>(
                [...stringArray, ...numberArray],
                isUnion<string | number>(isString, isNumber),
            ),
        ).toBeTruthy();
    });
    it('returns false for negatively tested array values', () => {
        expect(isArray<string>(stringArray, isNumber)).toBeFalsy();
        expect(isArray<number>(numberArray, isString)).toBeFalsy();
        expect(isArray<symbol>(symbolArray, isObject)).toBeFalsy();
        expect(isArray<object>(recordArray, isSymbol)).toBeFalsy();
        expect(
            isArray<string | number>(
                [...symbolArray, ...recordArray],
                isUnion<string | number>(isString, isNumber),
            ),
        ).toBeFalsy();
    });
    it('returns false for non-array values', () => {
        expect(isArray<string>('', isString)).toBeFalsy();
        expect(isArray<string>(null, isString)).toBeFalsy();
        expect(isArray<string>(123, isString)).toBeFalsy();
        expect(isArray<string>(Symbol(), isString)).toBeFalsy();
        expect(isArray<string>({}, isString)).toBeFalsy();
    });
    it('throws error when throwError = true', () => {
        expect(() => isArray<string>('', isString, true)).toThrow();
        expect(() => isArray<string>(null, isString, true)).toThrow();
        expect(() => isArray<string>(123, isString, true)).toThrow();
    });
    it('guards type correctly', () => {
        const unknownArray: unknown = [...stringArray];
        if (isArray<string>(unknownArray, isString)) {
            expectTypeOf(unknownArray).toMatchTypeOf(stringArray);
        }
    });
});

Note the last it() where I use the expect-type library. This is the only way i figured that actually works for testing generics with this library. Is there another solution or maybe a planned fix?

@mmkal
Copy link
Owner

mmkal commented Sep 2, 2021

What you have there doesn't look too bad to me, although I agree it'd be nice to fit in one statement. One possible way that avoids a runtime check (untested, but could be worth a try):

const isStringArray = (x: unknown) => isArray<string>(x, isString)
expectTypeOf(isStringArray).guards.toEqualTypeOf<string[]>()

Is there a particular API you would like to see here?

@Goldziher
Copy link
Author

I'm not sure about the api. I would though like an api that allows testing generics.

@mmkal mmkal transferred this issue from mmkal/ts Sep 25, 2022
@mmkal
Copy link
Owner

mmkal commented May 10, 2023

I'm not sure it's possible to propagate generics... generically. But @Goldziher if you think of a specific way you would like your test to look, please reopen/comment/create a new issue with that. You can write some imaginary code if you like. Closing until that point.

@mmkal mmkal closed this as completed May 10, 2023
renovate bot added a commit that referenced this issue Feb 3, 2025
##### [v10.2.0](https://github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1020)

##### Minor Changes

-   Packages executed via `pnpm dlx` and `pnpm create` are allowed to be built (run postinstall scripts) by default.

    If the packages executed by `dlx` or `create` have dependencies that have to be built, they should be listed via the `--allow-build` flag. For instance, if you want to run a package called `bundle` that has `esbuild` in dependencies and want to allow `esbuild` to run postinstall scripts, run:

        pnpm --allow-build=esbuild dlx bundle

    Related PR: [#9026](pnpm/pnpm#9026).

##### Patch Changes

-   Quote args for scripts with shell-quote to support new lines (on POSIX only) [#8980](pnpm/pnpm#8980).
-   Fix a bug in which `pnpm deploy` fails to read the correct `projectId` when the deploy source is the same as the workspace directory [#9001](pnpm/pnpm#9001).
-   Proxy settings should be respected, when resolving Git-hosted dependencies [#6530](pnpm/pnpm#6530).
-   Prevent `overrides` from adding invalid version ranges to `peerDependencies` by keeping the `peerDependencies` and overriding them with prod `dependencies` [#8978](pnpm/pnpm#8978).
-   Sort the package names in the "pnpm.onlyBuiltDependencies" list saved by `pnpm approve-builds`.
##### [v10.1.0](https://github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1010)

##### Minor Changes

-   Added a new command for printing the list of dependencies with ignored build scripts: `pnpm ignored-builds` [#8963](pnpm/pnpm#8963).
-   Added a new command for approving dependencies for running scripts during installation: `pnpm approve-builds` [#8963](pnpm/pnpm#8963).
-   Added a new setting called `optimistic-repeat-install`. When enabled, a fast check will be performed before proceeding to installation. This way a repeat install or an install on a project with everything up-to-date becomes a lot faster. But some edge cases might arise, so we keep it disabled by default for now [#8977](pnpm/pnpm#8977).
-   Added a new field "pnpm.ignoredBuiltDependencies" for explicitly listing packages that should not be built. When a package is in the list, pnpm will not print an info message about that package not being built [#8935](pnpm/pnpm#8935).

##### Patch Changes

-   Verify that the package name is valid when executing the publish command.
-   When running `pnpm install`, the `preprepare` and `postprepare` scripts of the project should be executed [#8989](pnpm/pnpm#8989).
-   Allow `workspace:` and `catalog:` to be part of wider version range in `peerDependencies`.
-   `pnpm deploy` should inherit the `pnpm` object from the root `package.json` [#8991](pnpm/pnpm#8991).
-   Make sure that the deletion of a `node_modules` in a sub-project of a monorepo is detected as out-of-date [#8959](pnpm/pnpm#8959).
-   Fix infinite loop caused by lifecycle scripts using `pnpm` to execute other scripts during `pnpm install` with `verify-deps-before-run=install` [#8954](pnpm/pnpm#8954).
-   Replace `strip-ansi` with the built-in `util.stripVTControlCharacters` [#9009](pnpm/pnpm#9009).
-   Do not print patched dependencies as ignored dependencies that require a build [#8952](pnpm/pnpm#8952).
##### [v10.0.0](https://github.com/pnpm/pnpm/blob/HEAD/pnpm/CHANGELOG.md#1000)

##### Major Changes

-   Lifecycle scripts of dependencies are not executed during installation by default! This is a breaking change aimed at increasing security. In order to allow lifecycle scripts of specific dependencies, they should be listed in the `pnpm.onlyBuiltDependencies` field of `package.json` [#8897](pnpm/pnpm#8897). For example:

    ```json
    {
      "pnpm": {
        "onlyBuiltDependencies": ["fsevents"]
      }
    }
    ```

-   `pnpm link` behavior updated:

    The `pnpm link` command now adds overrides to the root `package.json`.

    -   In a workspace: The override is added to the root of the workspace, linking the dependency to all projects in the workspace.
    -   Global linking: To link a package globally, run `pnpm link` from the package’s directory. Previously, you needed to use `pnpm link -g`.
        Related PR: [#8653](pnpm/pnpm#8653)

-   Secure hashing with SHA256:

    Various hashing algorithms have been updated to SHA256 for enhanced security and consistency:

    -   Long paths inside `node_modules/.pnpm` are now hashed with SHA256.
    -   Long peer dependency hashes in the lockfile now use SHA256 instead of MD5. (This affects very few users since these are only used for long keys.)
    -   The hash stored in the `packageExtensionsChecksum` field of `pnpm-lock.yaml` is now SHA256.
    -   The side effects cache keys now use SHA256.
    -   The pnpmfile checksum in the lockfile now uses SHA256 ([#8530](pnpm/pnpm#8530)).

-   Configuration updates:

    -   `manage-package-manager-versions`: enabled by default. pnpm now manages its own version based on the `packageManager` field in `package.json` by default.

    -   `public-hoist-pattern`: nothing is hoisted by default. Packages containing `eslint` or `prettier` in their name are no longer hoisted to the root of `node_modules`. Related Issue: [#8378](pnpm/pnpm#8378)

    -   Upgraded `@yarnpkg/extensions` to v2.0.3. This may alter your lockfile.

    -   `virtual-store-dir-max-length`: the default value on Windows has been reduced to 60 characters.

    -   Reduced environment variables for scripts:
        During script execution, fewer `npm_package_*` environment variables are set. Only `name`, `version`, `bin`, `engines`, and `config` remain.
        Related Issue: [#8552](pnpm/pnpm#8552)

    -   All dependencies are now installed even if `NODE_ENV=production`. Related Issue: [#8827](pnpm/pnpm#8827)

-   Changes to the global store:

    -   Store version bumped to v10.

    -   Some registries allow identical content to be published under different package names or versions. To accommodate this, index files in the store are now stored using both the content hash and package identifier.

        This approach ensures that we can:

        1.  Validate that the integrity in the lockfile corresponds to the correct package, which might not be the case after a poorly resolved Git conflict.
        2.  Allow the same content to be referenced by different packages or different versions of the same package.
            Related PR: [#8510](pnpm/pnpm#8510)
            Related Issue: [#8204](pnpm/pnpm#8204)

    -   More efficient side effects indexing. The structure of index files in the store has changed. Side effects are now tracked more efficiently by listing only file differences rather than all files.
        Related PR: [#8636](pnpm/pnpm#8636)

    -   A new `index` directory stores package content mappings. Previously, these files were in `files`.

-   Other breaking changes:
    -   The `#` character is now escaped in directory names within `node_modules/.pnpm`.
        Related PR: [#8557](pnpm/pnpm#8557)
    -   Running `pnpm add --global pnpm` or `pnpm add --global @pnpm/exe` now fails with an error message, directing you to use `pnpm self-update` instead.
        Related PR: [#8728](pnpm/pnpm#8728)
    -   Dependencies added via a URL now record the final resolved URL in the lockfile, ensuring that any redirects are fully captured.
        Related Issue: [#8833](pnpm/pnpm#8833)
    -   The `pnpm deploy` command now only works in workspaces that have `inject-workspace-packages=true`. This limitation is introduced to allow us to create a proper lockfile for the deployed project using the workspace lockfile.
    -   Removed conversion from lockfile v6 to v9. If you need v6-to-v9 conversion, use pnpm CLI v9.
    -   `pnpm test` now passes all parameters after the `test` keyword directly to the underlying script. This matches the behavior of `pnpm run test`. Previously you needed to use the `--` prefix.
        Related PR: [#8619](pnpm/pnpm#8619)

-   `node-gyp` updated to version 11.

-   `pnpm deploy` now tries creating a dedicated lockfile from a shared lockfile for deployment. It will fallback to deployment without a lockfile if there is no shared lockfile or `force-legacy-deploy` is set to `true`.

##### Minor Changes

-   Added support for a new type of dependencies called "configurational dependencies". These dependencies are installed before all the other types of dependencies (before "dependencies", "devDependencies", "optionalDependencies").

    Configurational dependencies cannot have dependencies of their own or lifecycle scripts. They should be added using exact version and the integrity checksum. Example:

    ```json
    {
      "pnpm": {
        "configDependencies": {
          "my-configs": "1.0.0+sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="
        }
      }
    }
    ```

    Related RFC: [#8](pnpm/rfcs#8).
    Related PR: [#8915](pnpm/pnpm#8915).

-   New settings:

    -   New `verify-deps-before-run` setting. This setting controls how `pnpm` checks `node_modules` before running scripts:

        -   `install`: Automatically run `pnpm install` if `node_modules` is outdated.
        -   `warn`: Print a warning if `node_modules` is outdated.
        -   `prompt`: Prompt the user to confirm running `pnpm install` if `node_modules` is outdated.
        -   `error`: Throw an error if `node_modules` is outdated.
        -   `false`: Disable dependency checks.
            Related Issue: [#8585](pnpm/pnpm#8585)

    -   New `inject-workspace-packages` setting enables hard-linking all local workspace dependencies instead of symlinking them. Previously, this could be achieved using [`dependenciesMeta[].injected`](https://pnpm.io/package_json#dependenciesmetainjected), which remains supported.
        Related PR: [#8836](pnpm/pnpm#8836)

-   Faster repeat installs:

    On repeated installs, `pnpm` performs a quick check to ensure `node_modules` is up to date.
    Related PR: [#8838](pnpm/pnpm#8838)

-   `pnpm add` integrates with default workspace catalog:

    When adding a dependency, `pnpm add` checks the default workspace catalog. If the dependency and version requirement match the catalog, `pnpm add` uses the `catalog:` protocol. Without a specified version, it matches the catalog’s version. If it doesn’t match, it falls back to standard behavior.
    Related Issue: [#8640](pnpm/pnpm#8640)

-   `pnpm dlx` now resolves packages to their exact versions and uses these exact versions for cache keys. This ensures `pnpm dlx` always installs the latest requested packages.
    Related PR: [#8811](pnpm/pnpm#8811)

-   No `node_modules` validation on certain commands. Commands that should not modify `node_modules` (e.g., `pnpm install --lockfile-only`) no longer validate or purge `node_modules`.
    Related PR: [#8657](pnpm/pnpm#8657)
##### [v9.15.5](https://github.com/pnpm/pnpm/releases/tag/v9.15.5)

#### Patch Changes

-   Verify that the package name is valid when executing the publish command.
-   When running `pnpm install`, the `preprepare` and `postprepare` scripts of the project should be executed [#8989](pnpm/pnpm#8989).
-   Quote args for scripts with shell-quote to support new lines (on POSIX only) [#8980](pnpm/pnpm#8980).
-   Proxy settings should be respected, when resolving Git-hosted dependencies [#6530](pnpm/pnpm#6530).
-   Replace `strip-ansi` with the built-in `util.stripVTControlCharacters` [#9009](pnpm/pnpm#9009).

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/sanity.svg" width="180" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://figma.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/figma.svg" width="80" alt="Figma"></a>
      </td>
    </tr>
  </tbody>
</table>
#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://prisma.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/prisma.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/prisma_light.svg" />
            <img src="https://pnpm.io/img/users/prisma.svg" width="180" alt="Prisma" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://uscreen.de/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/uscreen.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/uscreen_light.svg" />
            <img src="https://pnpm.io/img/users/uscreen.svg" width="180" alt="u|screen" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.jetbrains.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/jetbrains.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/jetbrains.svg" />
            <img src="https://pnpm.io/img/users/jetbrains.svg" width="180" alt="JetBrains" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="120" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://route4me.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <img src="https://pnpm.io/img/users/route4me.svg" width="220" alt="Route4Me" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://canva.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <img src="https://pnpm.io/img/users/canva.svg" width="120" alt="Canva" />
        </a>
      </td>
    </tr>
  </tbody>
</table>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants