npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@starterlib/testing

v0.16.5

Published

Outils de test du socle frontal starter : harnais, fixtures, simulateurs, hote de developpement des remotes. Jamais en dependance de production.

Readme

@starterlib/testing

Outils de test du socle frontal starter. Jamais en dependance de production.

Installation

pnpm add -D @starterlib/testing axe-core

Quel pair sert quel point d'entree

| Point d'entree | Pairs importes a l'execution | Requis ou optionnel | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | @starterlib/testing | axe-core | requis | | @starterlib/testing/ui | @angular/core, @starterlib/ui | optionnels : a installer pour ce point seul | | @starterlib/testing/hote-dev | @angular/core, common/http, router, platform-browser, rxjs, @starterlib/core, @starterlib/ui (tronc, navigation, libelles-en) | optionnels : ceux qu'un remote a deja | | @starterlib/testing/assemblage | @playwright/test ; @axe-core/playwright a l'appel de manquementsDAccessibilite | optionnels : ceux des essais de navigateur |

Un projet qui teste sans Angular (des utilitaires, des jetons) n'installe que axe-core. Un projet qui monte un ecran du socle avec socleDeTest installe aussi @angular/core et @starterlib/ui, qu'il a deja. Un remote qui se lance seul (pnpm start) avec @starterlib/testing/hote-dev a deja tous les pairs de ce point d'entree ; il eprouve ce pnpm start en vrai navigateur avec @starterlib/testing/assemblage, s'il installe @playwright/test. La table est gardee par tools/verifier-paquets.mjs (DEPENDANCES_ADMISES, lot 153) : le tronc qui importerait Angular, ou testing/ui un autre pair, fait echouer pnpm paquets:check.

Contenu

verifierAccessibilite(container) : passe axe-core sur un fragment rendu et echoue en nommant chaque violation, sa regle et l'element fautif.

import { verifierAccessibilite } from '@starterlib/testing';

it("ne produit aucune violation d'accessibilite", async () => {
  const { container } = await render(MonComposant);
  await verifierAccessibilite(container);
});

⚠️ Sous jsdom, axe ne voit ni la mise en page ni la cascade CSS : le contraste, la taille des cibles et la visibilite reelle ne sont pas couverts. Ces controles demandent un vrai navigateur : le depot les mesure sur son banc, en Chromium (pnpm test:navigateur, voir le README de @starterlib/ui), et ce paquet n'en exporte que la mesure d'un sprite (mesurerUnSprite, plus bas). REGLES_DESACTIVEES_PAR_DEFAUT nomme les regles d'axe que la verification desactive d'office, chacune avec sa raison (color-contrast, que jsdom ne peut pas calculer), pour qu'un test puisse les afficher.

Une regle se desactive en plus par exemptions, avec sa raison ECRITE : quarante caracteres au moins (LONGUEUR_MINIMALE_D_UNE_RAISON, 0.10.2), sans quoi la verification leve. « faux positif » ou une chaine vide ne se relisent pas. gravitesBloquantes choisit les niveaux qui font echouer (toutes par defaut) :

await verifierAccessibilite(container, {
  exemptions: {
    'button-name': "Le nom est pose par la directive hote, absente de ce montage d'essai.",
  },
  gravitesBloquantes: ['serious', 'critical'],
});

amorcerJsdom() : comble ce que jsdom ne fournit pas — scrollIntoView (absent) et le defilement de la fenetre (present mais inerte, il journalise « Not implemented »). A poser une fois, en setupFiles :

// angular.json, cible `test` : "setupFiles": ["tools/amorce-de-test.ts"]
import { amorcerJsdom } from '@starterlib/testing';
amorcerJsdom();

elementsFocalisables(racine) : ce que la tabulation traverse, dans l'ordre du DOM. Les sentinelles d'un piege a focus du CDK (cdk-focus-trap-anchor) n'y sont pas : le focus qui en atteint une est aussitot renvoye dans le piege (0.10.2). annonces(racine) et attendreLAnnonce(racine, texte) : ce que disent les regions vivantes, sans attendre au hasard — l'echec dit ce qui a ete lu.

attendreLAnnonce guette les MUTATIONS du fragment, pas l'horloge (0.10.2) : elle marche sous minuteries reelles comme sous vi.useFakeTimers(). Sous de faux minuteurs, l'annonce qu'un minuteur pose arrive des que l'essai avance le temps, et l'echeance tombe quand le temps simule la depasse :

vi.useFakeTimers();
const annonce = attendreLAnnonce(document.body, 'Page 2');
await vi.advanceTimersByTimeAsync(delai.annonce);
await expect(annonce).resolves.toBe('Page 2');

exercerLesLibelles(catalogue, options) : lit CHAQUE entree d'un dictionnaire — une chaine telle quelle, une fonction appelee avec des arguments d'exemple, un sous-dictionnaire en profondeur — et rend les textes ({ cle, texte }). Une traduction qu'aucun essai n'appelle n'a jamais ete lue : une interpolation oubliee n'apparaissait qu'a l'ecran. Une entree a domaine ferme (un etat) recoit ses arguments representatifs par exemples :

for (const { cle, texte } of exercerLesLibelles(LIBELLES_EN, {
  exemples: { etatDeDocument: ['analyse'] },
})) {
  expect(texte.trim(), cle).not.toBe('');
  expect(texte, cle).not.toMatch(/undefined|NaN/);
}

poserMouvementReduit() : pose data-mouvement="reduit" sur <html>, comme StMouvement pour une personne qui l'a choisi, et rend la fonction qui REMET la valeur precedente (et non qui retire l'attribut). C'est la seule voie que jsdom sache poser ; la requete de media de la feuille de jetons (prefers-reduced-motion) se prouve en vrai navigateur.

simulerUneRequeteDeMedia(requete, correspond) : remplace matchMedia par une requete que l'on fait basculer (basculer), qui compte ses ecouteurs change (ecouteurs) et rend l'original (restaurer) ; toute autre requete repond « ne correspond pas ». Un service qui SUIT le systeme (StTheme sur prefers-color-scheme) se prouve sur un changement, et par le retrait de son ecouteur a la destruction (constat A1063).

@starterlib/testing/ui

import { socleDeTest } from '@starterlib/testing/ui';

const socle = socleDeTest({ locale: 'fr-FR' });
await render(MonEcran, { providers: socle.fournisseurs });
socle.locale.set('en-GB'); // les formats doivent suivre, sans rechargement

⚠️ Point d'entree separe parce qu'il importe @starterlib/ui : laisse dans le tronc, il ferait compiler tout le paquet de composants dans les tests d'un paquet qui ne s'en sert pas.

analyserUnSprite(texte) et mesurerUnSprite(source, options) : les regles de pnpm icones:check, pour qu'un remote mesure SON sprite metier au lieu de le croire. analyserUnSprite ne lit que le TEXTE (un <symbol> par icone, en fill="currentColor" sur une grille carree, des formes simples, aucun script, style, transform ni couleur litterale) et tourne partout. mesurerUnSprite ajoute la parite avec les noms attendus (noms), une encre ni hors de la grille de 24 ni perdue dans son cadre, et l'equilibre d'une serie de statut (serieDeStatut) : elle exige un VRAI moteur de rendu (Vitest navigateur, Playwright), et refuse sous jsdom au lieu de rendre des boites nulles.

import { mesurerUnSprite } from '@starterlib/testing/ui';

const rapport = await mesurerUnSprite('/assets/icones/sprite.svg', { noms: ['colis'] });
expect(rapport.defauts).toEqual([]);

⚠️ Exception documentee a « aucun acces reseau hors de @starterlib/core » (docs/plan-librairie.md) : passee une ADRESSE, mesurerUnSprite lit le sprite par un fetch nu, sans les intercepteurs du noyau — ni session, ni jeton, ni correlation. Admise parce que ce paquet ne part jamais en production : l'outil lit l'actif que sert le serveur de test. Un texte SVG (qui commence par <) est lu tel quel, sans requete.

A venir

Simulateurs de reseau et de session. Le banc en vrai navigateur, lui, existe depuis la 0.6.0 : il vit dans le depot (pnpm test:navigateur) et n'est pas publie.

L'hote de developpement d'un remote (@starterlib/testing/hote-dev, lot 269)

Ce que pnpm start sert, sans BFF ni coquille : les MEMES fournisseurs que la coquille (session, langue, navigation, jetons, sprite), une coquille dessinee (gabarit courant, ou pleine page pour un remote pleinePage), la navigation tiree du manifeste source, chaque route montee sous SON prefixe, la racine renvoyee a la premiere, et toute autre adresse nommee (session simulee comprise) au lieu d'une page blanche.

// src/main.ts : la carte d'importation d'abord (les paquets partages s'y resolvent), puis l'hote.
import { initFederation } from '@angular-architects/native-federation';

initFederation()
  .then(() => import('./bootstrap'))
  .then((m) => m.demarrer())
  .catch((erreur: unknown) => {
    console.error("l'hote de developpement n'a pas pu demarrer", erreur);
  });

// src/bootstrap.ts
import { demarrerLHoteDeDeveloppement } from '@starterlib/testing/hote-dev';
import manifeste from '../manifest.source.json' with { type: 'json' };
import simulations from './hote-dev/simulations.engendre.json' with { type: 'json' };
import { CHEMIN_DU_SPRITE_METIER } from './app/icones/sprite-genere';

export const demarrer = () =>
  demarrerLHoteDeDeveloppement({
    manifeste,
    modules: { './routes': () => import('./app/routes').then((m) => m.routes) },
    stubs: simulations.stubs,
    spriteMetier: CHEMIN_DU_SPRITE_METIER,
  });

Le backend est simule DANS le navigateur (BackendSimule), dans cet ordre : les simulations ecrites du remote (simulations, un etat ou un calcul), les stubs WireMock du service (starter-web simulations les recopie de ../src/mock, corps compris : une seule simulation, celle du profil mock du BFF), le catalogue socle de l'hote, puis le BFF (GET /bff/session, alice ouverte par defaut ; POST /bff/logout). Le reste (catalogues, sprites) va au serveur de developpement. Chaque reponse attend LATENCE_SIMULEE (250 ms), erreurs comprises : un essai la pose a 0.

Les etats vide et en erreur d'un ecran se regardent sans ecrire de simulation, par l'adresse de la page (lot 274) : ?simuler=vide rend chaque liste lue a l'API videe (un tableau [], une page par curseur vide et derniere ; une fiche passe telle quelle, une ecriture aussi), ?simuler=erreur fait repondre chaque appel d'API par une panne 503 PLATFORM_DEPENDENCY_UNAVAILABLE. Seuls les appels /api/... en changent : la session, les catalogues et les actifs restent servis. L'hote le dit en banniere ; retirer le parametre rend les simulations. Un essai remplace ETAT_SIMULE.

Chaque etat d'une page par son adresse (P08-10)

Pour qu'un ecran se valide page par page sans toucher au code, l'adresse de la page demande aussi :

| Parametre | Ce que l'hote fait | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ?simuler=lent | chaque appel d'API attend 3 s (LATENCE_LENTE_PAR_DEFAUT), ou ?latence=<ms> (60 s au plus), celui que le serveur de developpement sert compris ; le BFF et les catalogues gardent leur latence | | ?simuler=hors-ligne | la page s'ouvre (catalogues, session), puis le reseau tombe (StCoupureSimulee) : chaque requete echoue sans atteindre le serveur (statut 0), la sonde fetch de StConnectivite aussi, et elle dit hors ligne | | ?scenario=<nom> | un scenario nomme du remote passe AVANT tout le reste (ses simulations ecrites, puis ses stubs), et sa session s'il en change une ; seul, ou avec ?simuler=lent ou ?simuler=hors-ligne | | ?langue=fr ou en | la langue de l'interface au demarrage | | ?theme=clair ou sombre | le theme au demarrage | | ?horloge=<instant ISO> | l'horloge de la page part de cet instant, puis avance (reglerLHorloge) : echeances et decomptes s'ouvrent au meme point a chaque chargement |

⚠️ L'horloge est reglee, jamais arretee : arretee, elle bloquerait ce qui mesure le temps qui passe (debounceTime de RxJS reprogramme sans fin, l'avertissement d'inactivite ne viendrait plus). Elle ne vit que dans la page de l'hote : demarrerLHoteDeDeveloppement la regle avant le demarrage, et le livrable de production ne porte pas l'hote.

Les scenarios se declarent dans le fichier de scenarios du remote, a cote des simulations engendrees ; starter-web simulations --scenarios les liste avec leur adresse, et l'etape simulations de starter-web verify les juge. Le modele :

{
  "scenarios": {
    "liste-vide": {
      "description": "Aucun element : l'etat vide de la liste",
      "stubs": [
        {
          "request": { "method": "GET", "urlPath": "/api/v1/elements" },
          "response": { "status": 200, "jsonBody": { "content": [], "nextCursor": null } }
        }
      ]
    },
    "gestionnaire": {
      "description": "Session d'un gestionnaire",
      "session": { "nomAffiche": "Gabriel Essai", "roles": ["GESTIONNAIRE"] }
    }
  }
}
// src/bootstrap.ts : le fichier passe a l'hote, a cote des stubs.
import scenarios from './hote-dev/scenarios.json' with { type: 'json' };

export const demarrer = () =>
  demarrerLHoteDeDeveloppement({
    manifeste,
    modules,
    stubs: simulations.stubs,
    scenarios: scenarios.scenarios,
  });

Un scenario porte une description (dite dans la banniere), des stubs (le sous-ensemble de WireMock ci-dessous, corps EN LIGNE : jsonBody ou body) et une session (ce qu'elle change de la session simulee). En TypeScript, simulations y ajoute un calcul. Noms et donnees fictifs : aucune donnee personnelle reelle. La banniere de l'hote liste les scenarios, chacun par un lien qui recharge la page courante avec son nom (les autres parametres gardes), dit un scenario inconnu, l'etat simule et l'horloge reglee ; elle se masque.

Les stubs se rejouent dans un sous-ensemble de WireMock : method, url, urlPattern, urlPath, urlPathPattern, queryParameters et headers (equalTo, contains, matches, absent), bodyPatterns (equalTo, contains, matches, equalToJson, matchesJsonPath : $.a.b, ['a'], [0], [*], filtres [?(@.a == 'x')]), priority ; en reponse status, headers, jsonBody ou body, fixedDelayMilliseconds. L'en-tete Authorization exige est ignore (le BFF le poserait). Un stub hors de ce sous-ensemble est ecarte, et nomme.

⚠️ Jamais en production : la configuration production d'un remote remplace src/main.ts par un point d'entree vide, et l'etape securite de starter-web verify refuse un livrable qui porterait l'hote.

Les essais d'assemblage en vrai navigateur (@starterlib/testing/assemblage, lot 269)

Un point d'entree de NODE, pour le lanceur Playwright : il n'est jamais charge par une page. Il porte les outils generiques du banc d'assemblage du BFF, et ceux qu'un remote emploie pour eprouver son hote de developpement :

// e2e/playwright.config.ts du remote
import { defineConfig, devices } from '@playwright/test';
import { serveurDeLHoteDeDeveloppement } from '@starterlib/testing/assemblage';

export default defineConfig({
  testDir: '.',
  testMatch: '**/*.e2e.ts',
  webServer: serveurDeLHoteDeDeveloppement({ port: 4302 }),
  use: { baseURL: 'http://localhost:4302', locale: 'fr-FR' },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
// e2e/hote-dev.e2e.ts
import { expect, test } from '@playwright/test';
import {
  manquementsDAccessibilite,
  policeDessinee,
  surveillerLaConsole,
  surveillerLesViolations,
} from '@starterlib/testing/assemblage';

test('la racine mene a la route du manifeste, dans la police du socle', async ({ page }) => {
  const erreurs = surveillerLaConsole(page);
  const violations = await surveillerLesViolations(page);
  await page.goto('/');
  await expect(page).toHaveURL(/\/catalogue$/);
  expect((await policeDessinee(page, 'h1')).chargeeParLaPage).toBe(true);
  expect(await manquementsDAccessibilite(page)).toEqual([]);
  expect(await violations()).toEqual([]);
  expect(erreurs()).toEqual([]);
});

| Outil | Ce qu'il dit | | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | serveurDeLHoteDeDeveloppement({ port, delai, reutiliser, dossier }) | le webServer : starter-web start --port <port> par Node, jamais pnpm start (0.10.7) ; attend remoteEntry.json, pas la racine (page blanche avant la construction federee) ; 4 min | | surveillerLesViolations(page) | les violations de la politique de securite du contenu, « directive adresse », la politique Trusted Types refusee nommee ; a appeler avant le premier chargement | | surveillerLaConsole(page) | les erreurs de console et les exceptions non rattrapees ; pas les avertissements | | champ(page, libelle, { obligatoire, mention }) | un champ par son nom accessible COMPLET, mention d'obligation comprise (MENTION_OBLIGATOIRE_FR par defaut, MENTION_OBLIGATOIRE_EN) ; egalite stricte | | attendreLeTitre(page, titre) | le titre de niveau 1, attendu | | manquementsDAccessibilite(page) | axe sous ETIQUETTES_WCAG_AA (WCAG 2.0 a 2.2, A et AA), une ligne par regle, sur la page AU REPOS : animations et transitions en cours attendues (5 s au plus), sauf sans fin ou en pause | | commandesTropPetites(page) | les boutons et champs sous 24 x 24 px ; l'etiquette d'un champ masque est la cible mesuree | | deborde(page) | vrai quand la page defile horizontalement | | regionsTraversees(page, n) | les regions que la tabulation traverse, doublons consecutifs reduits | | policeDessinee(page, selecteur) | les polices qui PEIGNENT le texte direct de l'element (Chromium : protocole du navigateur), et si la page les a chargees ; getComputedStyle ne dit que la pile demandee |

Ce point d'entree n'a ni serveur statique a nonce, ni doubles partages de StSession, StLangue et StTheme, par decision (lot 269) : l'assemblage sous la vraie politique du BFF est pnpm start:federe (le BFF en profil mock, lot 285), qu'une copie de la politique ferait deriver ; l'hote de developpement pose deja les VRAIS services sur un backend simule, et un essai d'ecran garde des doubles dont il decide les reponses.

Les outils s'eprouvent en Chromium par pnpm test:assemblage (pages servies par interception, sous une politique reelle), que pnpm verify:navigateur joue.