@verdaccio/e2e-ui 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/features.cjs +26 -3
- package/build/cjs/features.cjs.map +1 -1
- package/build/cjs/index.cjs +7 -0
- package/build/cjs/index.cjs.map +1 -1
- package/build/cjs/tasks/publish.cjs +5 -4
- package/build/cjs/tasks/publish.cjs.map +1 -1
- package/build/cjs/tests/change-password.cjs +1 -2
- package/build/cjs/tests/change-password.cjs.map +1 -1
- package/build/cjs/tests/detail.cjs +33 -0
- package/build/cjs/tests/detail.cjs.map +1 -1
- package/build/cjs/tests/failure-modes.cjs +22 -0
- package/build/cjs/tests/failure-modes.cjs.map +1 -1
- package/build/cjs/tests/index.cjs +4 -0
- package/build/cjs/tests/manifest-rendering.cjs +73 -0
- package/build/cjs/tests/manifest-rendering.cjs.map +1 -0
- package/build/cjs/tests/session.cjs +49 -0
- package/build/cjs/tests/session.cjs.map +1 -0
- package/build/cjs/tests/settings.cjs +28 -0
- package/build/cjs/tests/settings.cjs.map +1 -1
- package/build/esm/features.js +26 -3
- package/build/esm/features.js.map +1 -1
- package/build/esm/index.js +6 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/tasks/publish.js +5 -4
- package/build/esm/tasks/publish.js.map +1 -1
- package/build/esm/tests/change-password.js +1 -2
- package/build/esm/tests/change-password.js.map +1 -1
- package/build/esm/tests/detail.js +33 -0
- package/build/esm/tests/detail.js.map +1 -1
- package/build/esm/tests/failure-modes.js +22 -0
- package/build/esm/tests/failure-modes.js.map +1 -1
- package/build/esm/tests/index.js +3 -1
- package/build/esm/tests/manifest-rendering.js +73 -0
- package/build/esm/tests/manifest-rendering.js.map +1 -0
- package/build/esm/tests/session.js +49 -0
- package/build/esm/tests/session.js.map +1 -0
- package/build/esm/tests/settings.js +28 -0
- package/build/esm/tests/settings.js.map +1 -1
- package/build/features.d.ts +60 -0
- package/build/index.d.ts +1 -1
- package/build/tasks/publish.d.ts +9 -0
- package/build/tests/index.d.ts +2 -0
- package/build/tests/manifest-rendering.d.ts +14 -0
- package/build/tests/session.d.ts +2 -0
- package/package.json +1 -1
package/build/cjs/features.cjs
CHANGED
|
@@ -6,7 +6,10 @@ var DEFAULT_FEATURES = {
|
|
|
6
6
|
resultClickNavigation: true
|
|
7
7
|
},
|
|
8
8
|
home: { publishedPackageRendering: true },
|
|
9
|
-
settings: {
|
|
9
|
+
settings: {
|
|
10
|
+
languageSwitcher: true,
|
|
11
|
+
localizedDates: false
|
|
12
|
+
},
|
|
10
13
|
signin: { validationTests: true },
|
|
11
14
|
layout: { themeSwitch: true },
|
|
12
15
|
publish: {
|
|
@@ -17,12 +20,24 @@ var DEFAULT_FEATURES = {
|
|
|
17
20
|
detail: {
|
|
18
21
|
versionPinned: true,
|
|
19
22
|
scopedWireFormat: true,
|
|
20
|
-
versionNotFound: false
|
|
23
|
+
versionNotFound: false,
|
|
24
|
+
staleVersionsNavigation: false
|
|
25
|
+
},
|
|
26
|
+
manifestRendering: {
|
|
27
|
+
stringForms: false,
|
|
28
|
+
gravatarAvatars: false,
|
|
29
|
+
developersWithoutEmail: false
|
|
30
|
+
},
|
|
31
|
+
session: {
|
|
32
|
+
expiredTokenLoggedOut: true,
|
|
33
|
+
expiredTokenPurged: false
|
|
21
34
|
},
|
|
22
35
|
failureModes: {
|
|
23
36
|
homeServerError: true,
|
|
24
37
|
homeNetworkError: false,
|
|
25
|
-
detailErrorState: false
|
|
38
|
+
detailErrorState: false,
|
|
39
|
+
searchError: false,
|
|
40
|
+
downloadError: false
|
|
26
41
|
},
|
|
27
42
|
i18n: {
|
|
28
43
|
noRawKeysCorePages: true,
|
|
@@ -73,6 +88,14 @@ function mergeFeatures(defaults, overrides) {
|
|
|
73
88
|
...defaults.detail,
|
|
74
89
|
...overrides.detail
|
|
75
90
|
},
|
|
91
|
+
manifestRendering: {
|
|
92
|
+
...defaults.manifestRendering,
|
|
93
|
+
...overrides.manifestRendering
|
|
94
|
+
},
|
|
95
|
+
session: {
|
|
96
|
+
...defaults.session,
|
|
97
|
+
...overrides.session
|
|
98
|
+
},
|
|
76
99
|
failureModes: {
|
|
77
100
|
...defaults.failureModes,
|
|
78
101
|
...overrides.failureModes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features.cjs","names":[],"sources":["../../src/features.ts"],"sourcesContent":["import type { DeepPartial } from './testIds';\n\n/**\n * Per-test feature flags for enabling/disabling individual test cases\n * in the e2e-ui suites.\n *\n * The Verdaccio UI behaves differently across branches — search result\n * payload shape differs between the `/-/v1/search` and\n * `/-/verdaccio/data/search/*` endpoints, the language picker was\n * added in a specific minor, etc. Rather than forking the suite per\n * branch, consumers can disable individual tests via\n * `createRegistryConfig({ features: { … } })`.\n *\n * Most flags default to `true` (test runs). Compatibility probes for\n * unreleased server behavior may default to `false`; enable them in\n * consumers that run against a Verdaccio build with the feature.\n * A disabled flag converts the test into a Mocha `it.skip` call — the\n * suite still reports the test but marks it as pending.\n */\nexport interface Features {\n search: {\n /**\n * Whether to run the \"results dropdown renders a matching\n * package\" test. Disable on builds where the search result shape\n * or the Autocomplete rendering differs from the default assumption.\n */\n resultsDropdown: boolean;\n /**\n * Whether to run the \"clicking a result navigates to detail\" test.\n * Depends on the Autocomplete `onSelectItem` → router wiring.\n */\n resultClickNavigation: boolean;\n };\n home: {\n /**\n * Whether to run the `with a published package` sub-block inside\n * `homeTests` (publishes a throwaway package and asserts it\n * renders in the package list).\n */\n publishedPackageRendering: boolean;\n };\n settings: {\n /**\n * Whether to run the \"change language via card click\" test.\n * Depends on the `LanguageSwitch` component layout and its\n * translation sentinels (\"Translations\", \"German\"). Skip on\n * builds that lag behind the upstream translation file.\n */\n languageSwitcher: boolean;\n };\n signin: {\n /**\n * Whether to run the three validation tests (disabled submit\n * button, invalid credentials error banner). Depends on the\n * current yup schema and the hard-coded \"Invalid username or\n * password\" error string.\n */\n validationTests: boolean;\n };\n layout: {\n /**\n * Whether to run the dark/light theme switch test. Depends on\n * `web.showThemeSwitch` (defaults to true in ui-theme) and the\n * `header--button--light` / `header--button--dark` testids.\n */\n themeSwitch: boolean;\n };\n publish: {\n /**\n * Whether to run the \"tarball download button fires a GET\" test.\n * Depends on `web.showDownloadTarball` (defaults to true) and\n * the published package manifest having a valid `dist.tarball`.\n */\n downloadTarball: boolean;\n /**\n * Whether to run the private package tarball download tests from\n * the Web UI package list and sidebar.\n *\n * Enable only on Verdaccio builds that accept Web UI bearer tokens\n * on protected npm package/tarball routes. Verdaccio 6.x currently\n * returns HTTP 401 for this flow; the regression is tracked in\n * verdaccio/verdaccio#5765.\n */\n privateDownloadTarball: boolean;\n /**\n * Whether to run the \"raw viewer dialog opens + closes\" test.\n * Depends on `web.showRaw` (defaults to true).\n */\n rawViewer: boolean;\n };\n detail: {\n /**\n * Whether to run the \"visiting /v/<version> pins that version\" test\n * (asserts the sidebar request carries `?v=<version>`).\n */\n versionPinned: boolean;\n /**\n * Whether to run the wire-format test for scoped packages: the UI\n * must request `/-/verdaccio/data/sidebar/@scope/name` with the `@`\n * and `/` literal (npm registry convention), never `%40`-encoded.\n * Pins the regression caught by these suites in\n * verdaccio/verdaccio#6210.\n */\n scopedWireFormat: boolean;\n /**\n * Whether to run the \"nonexistent version renders Not Found\" test.\n * Requires the server to answer 404 for unknown `?v=` values\n * (verdaccio/verdaccio#6210); released lines silently fall back to\n * `latest`, so this defaults to `false` as a probe.\n */\n versionNotFound: boolean;\n };\n failureModes: {\n /**\n * Whether to run the \"backend 5xx on the package list must not\n * render the empty-registry onboarding\" test. Holds on every line\n * (a 5xx carries an HTTP code the UI always treated as an error).\n */\n homeServerError: boolean;\n /**\n * Whether to run the network-failure variant (request never\n * reaches the server). Released UIs treat a network failure as an\n * empty registry and show the onboarding card\n * (verdaccio/verdaccio#6210 fixes it), so this defaults to `false`\n * as a probe.\n */\n homeNetworkError: boolean;\n /**\n * Whether to run the \"5xx on the detail page renders an error\n * state instead of a blank page\" test. Requires the generic error\n * page from verdaccio/verdaccio#6210; released UIs render blank,\n * so this defaults to `false` as a probe.\n */\n detailErrorState: boolean;\n };\n i18n: {\n /**\n * Whether to scan the home and detail pages for raw i18n keys\n * (visible text like `sidebar.detail.version`). Holds on every\n * released line — these pages ship fully translated.\n */\n noRawKeysCorePages: boolean;\n /**\n * Whether to scan the security pages (login dialog, /-/web/login,\n * add-user, change-password) for raw i18n keys. The whole\n * `security.*` namespace is missing from every published ui-theme\n * bundle (fixed in verdaccio/verdaccio#6210), so this defaults to\n * `false` as a probe.\n */\n noRawKeysSecurityPages: boolean;\n };\n signup: {\n /**\n * Whether to run the create-user happy path (fill the form on\n * /-/web/add-user, submit, land on the success page) and assert\n * the wire contract: `PUT /-/verdaccio/sec/signup` with a 36-char\n * `sessionId`. Requires the server flag `createUser: true` AND the\n * fixed signup form from verdaccio/verdaccio#6210 — every\n * published ui-theme posts to a URL that only serves the SPA, so\n * this defaults to `false` as a probe.\n */\n happyPath: boolean;\n /**\n * Whether to run the client-side validation tests on the add-user\n * form (invalid email shows a message, url-unsafe username blocks\n * submit). Same requirements as `happyPath`.\n */\n validation: boolean;\n };\n changePassword: {\n /**\n * Whether to run the happy-path test (submit valid change,\n * expect navigation to the success page, then restore the\n * original password in `after()`).\n *\n * The suite targets /-/web/change-password, which renders only\n * when the server is configured with `flags.changePassword: true`.\n * Disable on registries that do not enable the flag.\n *\n * Also disable on **published verdaccio 6.x** (all lines through\n * 6.5.0): the reset_password handler in\n * `verdaccio/build/api/web/api/user.js` ships with an inverted\n * conditional — `validatePassword(...) === false` gates the\n * `auth.changePassword(...)` call, so a *valid* new password\n * always returns HTTP 400 (`PASSWORD_VALIDATION`). The bug is\n * fixed on the development branch but has not been released\n * in any 6.x tag, so the happy path cannot succeed against an\n * `npm install verdaccio@6` runtime.\n */\n happyPath: boolean;\n /**\n * Whether to run the client-side validation tests (submit button\n * stays disabled while fields are empty / mismatched confirm).\n * Depends on the yup `changePasswordSchema`.\n */\n validation: boolean;\n /**\n * Whether to run the \"wrong old password shows error banner\" test.\n * Depends on the server rejecting the call and the onSubmit catch\n * block surfacing `\"Failed to change password\"` via\n * `LoginDialogFormError`.\n */\n wrongOldPassword: boolean;\n };\n}\n\n/** Defaults: all flags on. */\nexport const DEFAULT_FEATURES: Features = {\n search: {\n resultsDropdown: true,\n resultClickNavigation: true,\n },\n home: {\n publishedPackageRendering: true,\n },\n settings: {\n languageSwitcher: true,\n },\n signin: {\n validationTests: true,\n },\n layout: {\n themeSwitch: true,\n },\n publish: {\n downloadTarball: true,\n privateDownloadTarball: false,\n rawViewer: true,\n },\n detail: {\n versionPinned: true,\n scopedWireFormat: true,\n versionNotFound: false,\n },\n failureModes: {\n homeServerError: true,\n homeNetworkError: false,\n detailErrorState: false,\n },\n i18n: {\n noRawKeysCorePages: true,\n noRawKeysSecurityPages: false,\n },\n signup: {\n happyPath: false,\n validation: false,\n },\n changePassword: {\n happyPath: true,\n validation: true,\n wrongOldPassword: true,\n },\n};\n\n/**\n * Merge user overrides into the default feature flags. Per-section,\n * one level deep — matching the style of `mergeTestIds`.\n */\nexport function mergeFeatures(defaults: Features, overrides?: DeepPartial<Features>): Features {\n if (!overrides) return defaults;\n return {\n search: { ...defaults.search, ...overrides.search },\n home: { ...defaults.home, ...overrides.home },\n settings: { ...defaults.settings, ...overrides.settings },\n signin: { ...defaults.signin, ...overrides.signin },\n layout: { ...defaults.layout, ...overrides.layout },\n publish: { ...defaults.publish, ...overrides.publish },\n detail: { ...defaults.detail, ...overrides.detail },\n failureModes: { ...defaults.failureModes, ...overrides.failureModes },\n i18n: { ...defaults.i18n, ...overrides.i18n },\n signup: { ...defaults.signup, ...overrides.signup },\n changePassword: { ...defaults.changePassword, ...overrides.changePassword },\n };\n}\n\n/**\n * Helper that returns either `it` or `it.skip` depending on an\n * enabled flag. Usage:\n *\n * maybeIt(features.search.resultsDropdown)('…', () => { … });\n */\nexport function maybeIt(enabled: boolean): Mocha.TestFunction | Mocha.PendingTestFunction {\n return enabled ? it : it.skip;\n}\n"],"mappings":";;AA+MA,IAAa,mBAA6B;CACxC,QAAQ;EACN,iBAAiB;EACjB,uBAAuB;CACzB;CACA,MAAM,EACJ,2BAA2B,KAC7B;CACA,UAAU,EACR,kBAAkB,KACpB;CACA,QAAQ,EACN,iBAAiB,KACnB;CACA,QAAQ,EACN,aAAa,KACf;CACA,SAAS;EACP,iBAAiB;EACjB,wBAAwB;EACxB,WAAW;CACb;CACA,QAAQ;EACN,eAAe;EACf,kBAAkB;EAClB,iBAAiB;CACnB;CACA,cAAc;EACZ,iBAAiB;EACjB,kBAAkB;EAClB,kBAAkB;CACpB;CACA,MAAM;EACJ,oBAAoB;EACpB,wBAAwB;CAC1B;CACA,QAAQ;EACN,WAAW;EACX,YAAY;CACd;CACA,gBAAgB;EACd,WAAW;EACX,YAAY;EACZ,kBAAkB;CACpB;AACF;;;;;AAMA,SAAgB,cAAc,UAAoB,WAA6C;CAC7F,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,MAAM;GAAE,GAAG,SAAS;GAAM,GAAG,UAAU;EAAK;EAC5C,UAAU;GAAE,GAAG,SAAS;GAAU,GAAG,UAAU;EAAS;EACxD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,UAAU;EAAQ;EACrD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,cAAc;GAAE,GAAG,SAAS;GAAc,GAAG,UAAU;EAAa;EACpE,MAAM;GAAE,GAAG,SAAS;GAAM,GAAG,UAAU;EAAK;EAC5C,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,gBAAgB;GAAE,GAAG,SAAS;GAAgB,GAAG,UAAU;EAAe;CAC5E;AACF;;;;;;;AAQA,SAAgB,QAAQ,SAAkE;CACxF,OAAO,UAAU,KAAK,GAAG;AAC3B"}
|
|
1
|
+
{"version":3,"file":"features.cjs","names":[],"sources":["../../src/features.ts"],"sourcesContent":["import type { DeepPartial } from './testIds';\n\n/**\n * Per-test feature flags for enabling/disabling individual test cases\n * in the e2e-ui suites.\n *\n * The Verdaccio UI behaves differently across branches — search result\n * payload shape differs between the `/-/v1/search` and\n * `/-/verdaccio/data/search/*` endpoints, the language picker was\n * added in a specific minor, etc. Rather than forking the suite per\n * branch, consumers can disable individual tests via\n * `createRegistryConfig({ features: { … } })`.\n *\n * Most flags default to `true` (test runs). Compatibility probes for\n * unreleased server behavior may default to `false`; enable them in\n * consumers that run against a Verdaccio build with the feature.\n * A disabled flag converts the test into a Mocha `it.skip` call — the\n * suite still reports the test but marks it as pending.\n */\nexport interface Features {\n search: {\n /**\n * Whether to run the \"results dropdown renders a matching\n * package\" test. Disable on builds where the search result shape\n * or the Autocomplete rendering differs from the default assumption.\n */\n resultsDropdown: boolean;\n /**\n * Whether to run the \"clicking a result navigates to detail\" test.\n * Depends on the Autocomplete `onSelectItem` → router wiring.\n */\n resultClickNavigation: boolean;\n };\n home: {\n /**\n * Whether to run the `with a published package` sub-block inside\n * `homeTests` (publishes a throwaway package and asserts it\n * renders in the package list).\n */\n publishedPackageRendering: boolean;\n };\n settings: {\n /**\n * Whether to run the \"change language via card click\" test.\n * Depends on the `LanguageSwitch` component layout and its\n * translation sentinels (\"Translations\", \"German\"). Skip on\n * builds that lag behind the upstream translation file.\n */\n languageSwitcher: boolean;\n /**\n * Whether to assert that dates follow the selected language\n * (dayjs locale). Released UIs never load the dayjs locale, so\n * relative dates stay English in every language (fixed in\n * verdaccio/verdaccio#6210) — probe.\n */\n localizedDates: boolean;\n };\n signin: {\n /**\n * Whether to run the three validation tests (disabled submit\n * button, invalid credentials error banner). Depends on the\n * current yup schema and the hard-coded \"Invalid username or\n * password\" error string.\n */\n validationTests: boolean;\n };\n layout: {\n /**\n * Whether to run the dark/light theme switch test. Depends on\n * `web.showThemeSwitch` (defaults to true in ui-theme) and the\n * `header--button--light` / `header--button--dark` testids.\n */\n themeSwitch: boolean;\n };\n publish: {\n /**\n * Whether to run the \"tarball download button fires a GET\" test.\n * Depends on `web.showDownloadTarball` (defaults to true) and\n * the published package manifest having a valid `dist.tarball`.\n */\n downloadTarball: boolean;\n /**\n * Whether to run the private package tarball download tests from\n * the Web UI package list and sidebar.\n *\n * Enable only on Verdaccio builds that accept Web UI bearer tokens\n * on protected npm package/tarball routes. Verdaccio 6.x currently\n * returns HTTP 401 for this flow; the regression is tracked in\n * verdaccio/verdaccio#5765.\n */\n privateDownloadTarball: boolean;\n /**\n * Whether to run the \"raw viewer dialog opens + closes\" test.\n * Depends on `web.showRaw` (defaults to true).\n */\n rawViewer: boolean;\n };\n detail: {\n /**\n * Whether to run the \"visiting /v/<version> pins that version\" test\n * (asserts the sidebar request carries `?v=<version>`).\n */\n versionPinned: boolean;\n /**\n * Whether to run the wire-format test for scoped packages: the UI\n * must request `/-/verdaccio/data/sidebar/@scope/name` with the `@`\n * and `/` literal (npm registry convention), never `%40`-encoded.\n * Pins the regression caught by these suites in\n * verdaccio/verdaccio#6210.\n */\n scopedWireFormat: boolean;\n /**\n * Whether to run the \"nonexistent version renders Not Found\" test.\n * Requires the server to answer 404 for unknown `?v=` values\n * (verdaccio/verdaccio#6210); released lines silently fall back to\n * `latest`, so this defaults to `false` as a probe.\n */\n versionNotFound: boolean;\n /**\n * Whether to run the \"versions tab shows the CURRENT package after\n * SPA navigation\" test. The detail routes reuse one mounted\n * component, and released UIs cache the previous package's versions\n * in local state (fixed in verdaccio/verdaccio#6210) — defaults to\n * `false` as a probe.\n */\n staleVersionsNavigation: boolean;\n };\n manifestRendering: {\n /**\n * Whether to run the string-form manifest tests: `repository`,\n * `funding` and `bugs` in their string/array forms are valid npm\n * and must render (fixed in verdaccio/verdaccio#6210) — probe.\n */\n stringForms: boolean;\n /**\n * Whether to assert gravatar avatars render on the detail sidebar.\n * Released UIs read `avatar` while the sidebar endpoint sends\n * `_avatar` (fixed in verdaccio/verdaccio#6210) — probe.\n */\n gravatarAvatars: boolean;\n /**\n * Whether to assert contributors without email don't collapse into\n * one (fixed in verdaccio/verdaccio#6210) — probe.\n */\n developersWithoutEmail: boolean;\n };\n session: {\n /**\n * Whether to assert that an expired token in localStorage boots the\n * UI logged out (header shows the login button). Holds on every\n * released line.\n */\n expiredTokenLoggedOut: boolean;\n /**\n * Whether to assert the expired token is also PURGED from\n * localStorage on boot — otherwise the api client keeps attaching\n * it (fixed in verdaccio/verdaccio#6210) — probe.\n */\n expiredTokenPurged: boolean;\n };\n failureModes: {\n /**\n * Whether to run the \"backend 5xx on the package list must not\n * render the empty-registry onboarding\" test. Holds on every line\n * (a 5xx carries an HTTP code the UI always treated as an error).\n */\n homeServerError: boolean;\n /**\n * Whether to run the network-failure variant (request never\n * reaches the server). Released UIs treat a network failure as an\n * empty registry and show the onboarding card\n * (verdaccio/verdaccio#6210 fixes it), so this defaults to `false`\n * as a probe.\n */\n homeNetworkError: boolean;\n /**\n * Whether to run the \"5xx on the detail page renders an error\n * state instead of a blank page\" test. Requires the generic error\n * page from verdaccio/verdaccio#6210; released UIs render blank,\n * so this defaults to `false` as a probe.\n */\n detailErrorState: boolean;\n /**\n * Whether to run the \"search 5xx shows an error, not 'no results\n * found'\" test. The error state ships with verdaccio/verdaccio#6210\n * — probe.\n */\n searchError: boolean;\n /**\n * Whether to run the \"failed tarball download shows an error\n * snackbar\" test — clicking download and having nothing happen sent\n * users retrying blindly (fixed in verdaccio/verdaccio#6210) — probe.\n */\n downloadError: boolean;\n };\n i18n: {\n /**\n * Whether to scan the home and detail pages for raw i18n keys\n * (visible text like `sidebar.detail.version`). Holds on every\n * released line — these pages ship fully translated.\n */\n noRawKeysCorePages: boolean;\n /**\n * Whether to scan the security pages (login dialog, /-/web/login,\n * add-user, change-password) for raw i18n keys. The whole\n * `security.*` namespace is missing from every published ui-theme\n * bundle (fixed in verdaccio/verdaccio#6210), so this defaults to\n * `false` as a probe.\n */\n noRawKeysSecurityPages: boolean;\n };\n signup: {\n /**\n * Whether to run the create-user happy path (fill the form on\n * /-/web/add-user, submit, land on the success page) and assert\n * the wire contract: `PUT /-/verdaccio/sec/signup` with a 36-char\n * `sessionId`. Requires the server flag `createUser: true` AND the\n * fixed signup form from verdaccio/verdaccio#6210 — every\n * published ui-theme posts to a URL that only serves the SPA, so\n * this defaults to `false` as a probe.\n */\n happyPath: boolean;\n /**\n * Whether to run the client-side validation tests on the add-user\n * form (invalid email shows a message, url-unsafe username blocks\n * submit). Same requirements as `happyPath`.\n */\n validation: boolean;\n };\n changePassword: {\n /**\n * Whether to run the happy-path test (submit valid change,\n * expect navigation to the success page, then restore the\n * original password in `after()`).\n *\n * The suite targets /-/web/change-password, which renders only\n * when the server is configured with `flags.changePassword: true`.\n * Disable on registries that do not enable the flag.\n *\n * Also disable on **published verdaccio 6.x** (all lines through\n * 6.5.0): the reset_password handler in\n * `verdaccio/build/api/web/api/user.js` ships with an inverted\n * conditional — `validatePassword(...) === false` gates the\n * `auth.changePassword(...)` call, so a *valid* new password\n * always returns HTTP 400 (`PASSWORD_VALIDATION`). The bug is\n * fixed on the development branch but has not been released\n * in any 6.x tag, so the happy path cannot succeed against an\n * `npm install verdaccio@6` runtime.\n */\n happyPath: boolean;\n /**\n * Whether to run the client-side validation tests (submit button\n * stays disabled while fields are empty / mismatched confirm).\n * Depends on the yup `changePasswordSchema`.\n */\n validation: boolean;\n /**\n * Whether to run the \"wrong old password shows error banner\" test.\n * Depends on the server rejecting the call and the onSubmit catch\n * block surfacing `\"Failed to change password\"` via\n * `LoginDialogFormError`.\n */\n wrongOldPassword: boolean;\n };\n}\n\n/** Defaults: all flags on. */\nexport const DEFAULT_FEATURES: Features = {\n search: {\n resultsDropdown: true,\n resultClickNavigation: true,\n },\n home: {\n publishedPackageRendering: true,\n },\n settings: {\n languageSwitcher: true,\n localizedDates: false,\n },\n signin: {\n validationTests: true,\n },\n layout: {\n themeSwitch: true,\n },\n publish: {\n downloadTarball: true,\n privateDownloadTarball: false,\n rawViewer: true,\n },\n detail: {\n versionPinned: true,\n scopedWireFormat: true,\n versionNotFound: false,\n staleVersionsNavigation: false,\n },\n manifestRendering: {\n stringForms: false,\n gravatarAvatars: false,\n developersWithoutEmail: false,\n },\n session: {\n expiredTokenLoggedOut: true,\n expiredTokenPurged: false,\n },\n failureModes: {\n homeServerError: true,\n homeNetworkError: false,\n detailErrorState: false,\n searchError: false,\n downloadError: false,\n },\n i18n: {\n noRawKeysCorePages: true,\n noRawKeysSecurityPages: false,\n },\n signup: {\n happyPath: false,\n validation: false,\n },\n changePassword: {\n happyPath: true,\n validation: true,\n wrongOldPassword: true,\n },\n};\n\n/**\n * Merge user overrides into the default feature flags. Per-section,\n * one level deep — matching the style of `mergeTestIds`.\n */\nexport function mergeFeatures(defaults: Features, overrides?: DeepPartial<Features>): Features {\n if (!overrides) return defaults;\n return {\n search: { ...defaults.search, ...overrides.search },\n home: { ...defaults.home, ...overrides.home },\n settings: { ...defaults.settings, ...overrides.settings },\n signin: { ...defaults.signin, ...overrides.signin },\n layout: { ...defaults.layout, ...overrides.layout },\n publish: { ...defaults.publish, ...overrides.publish },\n detail: { ...defaults.detail, ...overrides.detail },\n manifestRendering: { ...defaults.manifestRendering, ...overrides.manifestRendering },\n session: { ...defaults.session, ...overrides.session },\n failureModes: { ...defaults.failureModes, ...overrides.failureModes },\n i18n: { ...defaults.i18n, ...overrides.i18n },\n signup: { ...defaults.signup, ...overrides.signup },\n changePassword: { ...defaults.changePassword, ...overrides.changePassword },\n };\n}\n\n/**\n * Helper that returns either `it` or `it.skip` depending on an\n * enabled flag. Usage:\n *\n * maybeIt(features.search.resultsDropdown)('…', () => { … });\n */\nexport function maybeIt(enabled: boolean): Mocha.TestFunction | Mocha.PendingTestFunction {\n return enabled ? it : it.skip;\n}\n"],"mappings":";;AA2QA,IAAa,mBAA6B;CACxC,QAAQ;EACN,iBAAiB;EACjB,uBAAuB;CACzB;CACA,MAAM,EACJ,2BAA2B,KAC7B;CACA,UAAU;EACR,kBAAkB;EAClB,gBAAgB;CAClB;CACA,QAAQ,EACN,iBAAiB,KACnB;CACA,QAAQ,EACN,aAAa,KACf;CACA,SAAS;EACP,iBAAiB;EACjB,wBAAwB;EACxB,WAAW;CACb;CACA,QAAQ;EACN,eAAe;EACf,kBAAkB;EAClB,iBAAiB;EACjB,yBAAyB;CAC3B;CACA,mBAAmB;EACjB,aAAa;EACb,iBAAiB;EACjB,wBAAwB;CAC1B;CACA,SAAS;EACP,uBAAuB;EACvB,oBAAoB;CACtB;CACA,cAAc;EACZ,iBAAiB;EACjB,kBAAkB;EAClB,kBAAkB;EAClB,aAAa;EACb,eAAe;CACjB;CACA,MAAM;EACJ,oBAAoB;EACpB,wBAAwB;CAC1B;CACA,QAAQ;EACN,WAAW;EACX,YAAY;CACd;CACA,gBAAgB;EACd,WAAW;EACX,YAAY;EACZ,kBAAkB;CACpB;AACF;;;;;AAMA,SAAgB,cAAc,UAAoB,WAA6C;CAC7F,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,MAAM;GAAE,GAAG,SAAS;GAAM,GAAG,UAAU;EAAK;EAC5C,UAAU;GAAE,GAAG,SAAS;GAAU,GAAG,UAAU;EAAS;EACxD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,UAAU;EAAQ;EACrD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,mBAAmB;GAAE,GAAG,SAAS;GAAmB,GAAG,UAAU;EAAkB;EACnF,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,UAAU;EAAQ;EACrD,cAAc;GAAE,GAAG,SAAS;GAAc,GAAG,UAAU;EAAa;EACpE,MAAM;GAAE,GAAG,SAAS;GAAM,GAAG,UAAU;EAAK;EAC5C,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;EAAO;EAClD,gBAAgB;GAAE,GAAG,SAAS;GAAgB,GAAG,UAAU;EAAe;CAC5E;AACF;;;;;;;AAQA,SAAgB,QAAQ,SAAkE;CACxF,OAAO,UAAU,KAAK,GAAG;AAC3B"}
|
package/build/cjs/index.cjs
CHANGED
|
@@ -13,6 +13,8 @@ const require_detail = require("./tests/detail.cjs");
|
|
|
13
13
|
const require_failure_modes = require("./tests/failure-modes.cjs");
|
|
14
14
|
const require_i18n_keys = require("./tests/i18n-keys.cjs");
|
|
15
15
|
const require_signup = require("./tests/signup.cjs");
|
|
16
|
+
const require_manifest_rendering = require("./tests/manifest-rendering.cjs");
|
|
17
|
+
const require_session = require("./tests/session.cjs");
|
|
16
18
|
require("./tests/index.cjs");
|
|
17
19
|
let fs = require("fs");
|
|
18
20
|
//#region src/index.ts
|
|
@@ -110,6 +112,7 @@ function setupVerdaccioTasks(on, options) {
|
|
|
110
112
|
version: input.version,
|
|
111
113
|
dependencies: input.dependencies,
|
|
112
114
|
devDependencies: input.devDependencies,
|
|
115
|
+
manifest: input.manifest,
|
|
113
116
|
unique: input.unique
|
|
114
117
|
});
|
|
115
118
|
},
|
|
@@ -169,6 +172,8 @@ function registerAllTests(config) {
|
|
|
169
172
|
require_detail.detailTests(config);
|
|
170
173
|
require_failure_modes.failureModeTests(config);
|
|
171
174
|
require_i18n_keys.i18nKeyTests(config);
|
|
175
|
+
require_manifest_rendering.manifestRenderingTests(config);
|
|
176
|
+
require_session.sessionTests(config);
|
|
172
177
|
require_signup.signupTests(config);
|
|
173
178
|
require_change_password.changePasswordTests(config);
|
|
174
179
|
}
|
|
@@ -184,11 +189,13 @@ exports.failureModeTests = require_failure_modes.failureModeTests;
|
|
|
184
189
|
exports.homeTests = require_home.homeTests;
|
|
185
190
|
exports.i18nKeyTests = require_i18n_keys.i18nKeyTests;
|
|
186
191
|
exports.layoutTests = require_layout.layoutTests;
|
|
192
|
+
exports.manifestRenderingTests = require_manifest_rendering.manifestRenderingTests;
|
|
187
193
|
exports.maybeIt = require_features.maybeIt;
|
|
188
194
|
exports.publishPackage = require_publish.publishPackage;
|
|
189
195
|
exports.publishTests = require_publish$1.publishTests;
|
|
190
196
|
exports.registerAllTests = registerAllTests;
|
|
191
197
|
exports.searchTests = require_search.searchTests;
|
|
198
|
+
exports.sessionTests = require_session.sessionTests;
|
|
192
199
|
exports.settingsTests = require_settings.settingsTests;
|
|
193
200
|
exports.setupVerdaccioTasks = setupVerdaccioTasks;
|
|
194
201
|
exports.signinTests = require_signin.signinTests;
|
package/build/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { appendFileSync } from 'fs';\n\nimport { DEFAULT_FEATURES, mergeFeatures } from './features';\nimport {\n PublishPackageResult,\n PublishPackageTaskInput,\n UnpublishPackageInput,\n UnpublishPackageResult,\n cleanupPublished,\n publishPackage,\n unpublishPackage,\n} from './tasks';\nimport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS, mergeSelectors, mergeTestIds } from './testIds';\n// Re-export for convenience\nimport {\n changePasswordTests,\n detailTests,\n failureModeTests,\n homeTests,\n i18nKeyTests,\n layoutTests,\n publishTests,\n searchTests,\n settingsTests,\n signinTests,\n signupTests,\n} from './tests';\nimport { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\n\nexport type { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\nexport type {\n PublishPackageInput,\n PublishPackageTaskInput,\n PublishPackageResult,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nexport type { DeepPartial, Selectors, TestIds } from './testIds';\nexport type { Features } from './features';\nexport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS } from './testIds';\nexport { DEFAULT_FEATURES, maybeIt } from './features';\nexport { publishPackage, cleanupPublished, unpublishPackage } from './tasks';\nexport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n changePasswordTests,\n detailTests,\n failureModeTests,\n i18nKeyTests,\n signupTests,\n} from './tests';\n\n/**\n * Build a full RegistryConfig from user-provided options with defaults.\n *\n * `testIds` and `selectors` are deep-merged per section with the\n * defaults in `./testIds`. Consumers targeting a non-default Verdaccio\n * build can override just the fields that drifted:\n *\n * createRegistryConfig({\n * registryUrl: 'http://localhost:4873',\n * testIds: {\n * header: { settingsTooltip: 'my-new-settings-btn' },\n * },\n * });\n */\nexport function createRegistryConfig(options: VerdaccioUiOptions): RegistryConfig {\n const url = new URL(options.registryUrl);\n return {\n registryUrl: options.registryUrl,\n port: options.port ?? (parseInt(url.port, 10) || 4873),\n credentials: options.credentials ?? { user: 'test', password: 'test' },\n title: options.title ?? 'Verdaccio',\n testIds: mergeTestIds(DEFAULT_TEST_IDS, options.testIds),\n selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors),\n features: mergeFeatures(DEFAULT_FEATURES, options.features),\n };\n}\n\n/**\n * Register Verdaccio Cypress tasks in setupNodeEvents.\n *\n * Usage in cypress.config.ts:\n * import { setupVerdaccioTasks } from '@verdaccio/e2e-ui';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * setupVerdaccioTasks(on, { registryUrl: 'http://localhost:4873' });\n * },\n * },\n * });\n */\nexport function setupVerdaccioTasks(on: Cypress.PluginEvents, options: VerdaccioUiOptions): void {\n const config = createRegistryConfig(options);\n\n // GitHub Actions Step Summary\n on('after:run', (results) => {\n const summaryFile = process.env.GITHUB_STEP_SUMMARY;\n if (!summaryFile || !results || !('runs' in results)) return;\n\n const cypressResults = results as CypressCommandLine.CypressRunResult;\n const lines: string[] = [];\n\n lines.push('## UI E2E Test Results\\n');\n lines.push('| Spec | Tests | Passing | Failing | Pending | Duration |');\n lines.push('|------|-------|---------|---------|---------|----------|');\n\n for (const run of cypressResults.runs) {\n const specName = run.spec.relative || run.spec.name;\n const stats = run.stats;\n const icon = stats.failures > 0 ? '❌' : '✅';\n const dur = stats.duration ? `${(stats.duration / 1000).toFixed(1)}s` : '-';\n lines.push(\n `| ${icon} ${specName} | ${stats.tests} | ${stats.passes} | ${stats.failures} | ${stats.pending} | ${dur} |`\n );\n }\n\n lines.push('');\n const totals = cypressResults.totalTests ?? 0;\n const passed = cypressResults.totalPassed ?? 0;\n const failed = cypressResults.totalFailed ?? 0;\n const pending = cypressResults.totalPending ?? 0;\n const emoji = failed > 0 ? '❌' : '✅';\n lines.push(\n `${emoji} **${passed} passed**, **${failed} failed**, **${pending} pending** (${totals} total)\\n`\n );\n\n appendFileSync(summaryFile, lines.join('\\n'));\n });\n\n on('task', {\n registry(): RegistryTaskResult {\n return {\n registryUrl: config.registryUrl,\n port: config.port,\n };\n },\n /**\n * Publish a throwaway package to the registry.\n *\n * Usage from a Cypress spec:\n *\n * cy.task('publishPackage', { pkgName: '@verdaccio/pkg-scoped' });\n *\n * Any field not provided falls back to the values configured here\n * (registryUrl, credentials). The task returns a PublishPackageResult.\n */\n async publishPackage(input: PublishPackageTaskInput): Promise<PublishPackageResult> {\n return publishPackage({\n registryUrl: input.registryUrl ?? config.registryUrl,\n credentials: input.credentials ?? config.credentials,\n pkgName: input.pkgName,\n version: input.version,\n dependencies: input.dependencies,\n devDependencies: input.devDependencies,\n unique: input.unique,\n });\n },\n /**\n * Remove a temp project folder returned by a previous publishPackage\n * call. Returns null (Cypress tasks must return something serializable).\n *\n * cy.task('cleanupPublished', result.tempFolder);\n */\n async cleanupPublished(tempFolder: string): Promise<null> {\n await cleanupPublished(tempFolder);\n return null;\n },\n /**\n * Unpublish a package from the registry so the next test starts\n * from a clean slate. Accepts either a package name or a full input\n * object (to reuse a prior tempFolder's token).\n *\n * cy.task('unpublishPackage', '@verdaccio/pkg-scoped');\n * cy.task('unpublishPackage', { pkgName, tempFolder });\n */\n async unpublishPackage(\n input:\n | string\n | (Omit<UnpublishPackageInput, 'registryUrl'> & {\n registryUrl?: string;\n })\n ): Promise<UnpublishPackageResult> {\n const normalized =\n typeof input === 'string'\n ? { pkgName: input, registryUrl: config.registryUrl }\n : { ...input, registryUrl: input.registryUrl ?? config.registryUrl };\n return unpublishPackage(normalized);\n },\n });\n}\n\n/**\n * Register all Verdaccio UI tests.\n *\n * Usage in a spec file:\n * import { registerAllTests, createRegistryConfig } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * registerAllTests(config);\n *\n * Or pick individual suites:\n * import { homeTests, signinTests } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * homeTests(config);\n * signinTests(config);\n */\nexport function registerAllTests(config: RegistryConfig): void {\n homeTests(config);\n signinTests(config);\n layoutTests(config);\n searchTests(config);\n settingsTests(config);\n publishTests(config);\n detailTests(config);\n failureModeTests(config);\n i18nKeyTests(config);\n signupTests(config);\n changePasswordTests(config);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { appendFileSync } from 'fs';\n\nimport { DEFAULT_FEATURES, mergeFeatures } from './features';\nimport {\n PublishPackageResult,\n PublishPackageTaskInput,\n UnpublishPackageInput,\n UnpublishPackageResult,\n cleanupPublished,\n publishPackage,\n unpublishPackage,\n} from './tasks';\nimport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS, mergeSelectors, mergeTestIds } from './testIds';\n// Re-export for convenience\nimport {\n changePasswordTests,\n detailTests,\n failureModeTests,\n homeTests,\n i18nKeyTests,\n layoutTests,\n manifestRenderingTests,\n publishTests,\n searchTests,\n sessionTests,\n settingsTests,\n signinTests,\n signupTests,\n} from './tests';\nimport { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\n\nexport type { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\nexport type {\n PublishPackageInput,\n PublishPackageTaskInput,\n PublishPackageResult,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nexport type { DeepPartial, Selectors, TestIds } from './testIds';\nexport type { Features } from './features';\nexport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS } from './testIds';\nexport { DEFAULT_FEATURES, maybeIt } from './features';\nexport { publishPackage, cleanupPublished, unpublishPackage } from './tasks';\nexport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n changePasswordTests,\n detailTests,\n failureModeTests,\n i18nKeyTests,\n manifestRenderingTests,\n sessionTests,\n signupTests,\n} from './tests';\n\n/**\n * Build a full RegistryConfig from user-provided options with defaults.\n *\n * `testIds` and `selectors` are deep-merged per section with the\n * defaults in `./testIds`. Consumers targeting a non-default Verdaccio\n * build can override just the fields that drifted:\n *\n * createRegistryConfig({\n * registryUrl: 'http://localhost:4873',\n * testIds: {\n * header: { settingsTooltip: 'my-new-settings-btn' },\n * },\n * });\n */\nexport function createRegistryConfig(options: VerdaccioUiOptions): RegistryConfig {\n const url = new URL(options.registryUrl);\n return {\n registryUrl: options.registryUrl,\n port: options.port ?? (parseInt(url.port, 10) || 4873),\n credentials: options.credentials ?? { user: 'test', password: 'test' },\n title: options.title ?? 'Verdaccio',\n testIds: mergeTestIds(DEFAULT_TEST_IDS, options.testIds),\n selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors),\n features: mergeFeatures(DEFAULT_FEATURES, options.features),\n };\n}\n\n/**\n * Register Verdaccio Cypress tasks in setupNodeEvents.\n *\n * Usage in cypress.config.ts:\n * import { setupVerdaccioTasks } from '@verdaccio/e2e-ui';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * setupVerdaccioTasks(on, { registryUrl: 'http://localhost:4873' });\n * },\n * },\n * });\n */\nexport function setupVerdaccioTasks(on: Cypress.PluginEvents, options: VerdaccioUiOptions): void {\n const config = createRegistryConfig(options);\n\n // GitHub Actions Step Summary\n on('after:run', (results) => {\n const summaryFile = process.env.GITHUB_STEP_SUMMARY;\n if (!summaryFile || !results || !('runs' in results)) return;\n\n const cypressResults = results as CypressCommandLine.CypressRunResult;\n const lines: string[] = [];\n\n lines.push('## UI E2E Test Results\\n');\n lines.push('| Spec | Tests | Passing | Failing | Pending | Duration |');\n lines.push('|------|-------|---------|---------|---------|----------|');\n\n for (const run of cypressResults.runs) {\n const specName = run.spec.relative || run.spec.name;\n const stats = run.stats;\n const icon = stats.failures > 0 ? '❌' : '✅';\n const dur = stats.duration ? `${(stats.duration / 1000).toFixed(1)}s` : '-';\n lines.push(\n `| ${icon} ${specName} | ${stats.tests} | ${stats.passes} | ${stats.failures} | ${stats.pending} | ${dur} |`\n );\n }\n\n lines.push('');\n const totals = cypressResults.totalTests ?? 0;\n const passed = cypressResults.totalPassed ?? 0;\n const failed = cypressResults.totalFailed ?? 0;\n const pending = cypressResults.totalPending ?? 0;\n const emoji = failed > 0 ? '❌' : '✅';\n lines.push(\n `${emoji} **${passed} passed**, **${failed} failed**, **${pending} pending** (${totals} total)\\n`\n );\n\n appendFileSync(summaryFile, lines.join('\\n'));\n });\n\n on('task', {\n registry(): RegistryTaskResult {\n return {\n registryUrl: config.registryUrl,\n port: config.port,\n };\n },\n /**\n * Publish a throwaway package to the registry.\n *\n * Usage from a Cypress spec:\n *\n * cy.task('publishPackage', { pkgName: '@verdaccio/pkg-scoped' });\n *\n * Any field not provided falls back to the values configured here\n * (registryUrl, credentials). The task returns a PublishPackageResult.\n */\n async publishPackage(input: PublishPackageTaskInput): Promise<PublishPackageResult> {\n return publishPackage({\n registryUrl: input.registryUrl ?? config.registryUrl,\n credentials: input.credentials ?? config.credentials,\n pkgName: input.pkgName,\n version: input.version,\n dependencies: input.dependencies,\n devDependencies: input.devDependencies,\n manifest: input.manifest,\n unique: input.unique,\n });\n },\n /**\n * Remove a temp project folder returned by a previous publishPackage\n * call. Returns null (Cypress tasks must return something serializable).\n *\n * cy.task('cleanupPublished', result.tempFolder);\n */\n async cleanupPublished(tempFolder: string): Promise<null> {\n await cleanupPublished(tempFolder);\n return null;\n },\n /**\n * Unpublish a package from the registry so the next test starts\n * from a clean slate. Accepts either a package name or a full input\n * object (to reuse a prior tempFolder's token).\n *\n * cy.task('unpublishPackage', '@verdaccio/pkg-scoped');\n * cy.task('unpublishPackage', { pkgName, tempFolder });\n */\n async unpublishPackage(\n input:\n | string\n | (Omit<UnpublishPackageInput, 'registryUrl'> & {\n registryUrl?: string;\n })\n ): Promise<UnpublishPackageResult> {\n const normalized =\n typeof input === 'string'\n ? { pkgName: input, registryUrl: config.registryUrl }\n : { ...input, registryUrl: input.registryUrl ?? config.registryUrl };\n return unpublishPackage(normalized);\n },\n });\n}\n\n/**\n * Register all Verdaccio UI tests.\n *\n * Usage in a spec file:\n * import { registerAllTests, createRegistryConfig } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * registerAllTests(config);\n *\n * Or pick individual suites:\n * import { homeTests, signinTests } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * homeTests(config);\n * signinTests(config);\n */\nexport function registerAllTests(config: RegistryConfig): void {\n homeTests(config);\n signinTests(config);\n layoutTests(config);\n searchTests(config);\n settingsTests(config);\n publishTests(config);\n detailTests(config);\n failureModeTests(config);\n i18nKeyTests(config);\n manifestRenderingTests(config);\n sessionTests(config);\n signupTests(config);\n changePasswordTests(config);\n}\n"],"mappings":"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"}
|
|
@@ -50,11 +50,9 @@ async function obtainLegacyToken(registryUrl) {
|
|
|
50
50
|
token: json.token
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
async function createTempProject(pkgName, version, registryUrl, token, dependencies, devDependencies) {
|
|
53
|
+
async function createTempProject(pkgName, version, registryUrl, token, dependencies, devDependencies, manifestOverrides = {}) {
|
|
54
54
|
const tempFolder = await (0, fs_promises.mkdtemp)((0, path.join)((0, os.tmpdir)(), `verdaccio-e2e-ui-${sanitizeFolderName(pkgName)}-`));
|
|
55
55
|
const manifest = {
|
|
56
|
-
name: pkgName,
|
|
57
|
-
version,
|
|
58
56
|
description: `e2e test fixture ${pkgName}`,
|
|
59
57
|
main: "index.js",
|
|
60
58
|
dependencies,
|
|
@@ -66,6 +64,9 @@ async function createTempProject(pkgName, version, registryUrl, token, dependenc
|
|
|
66
64
|
],
|
|
67
65
|
author: "Verdaccio E2E <verdaccio@example.org>",
|
|
68
66
|
license: "MIT",
|
|
67
|
+
...manifestOverrides,
|
|
68
|
+
name: pkgName,
|
|
69
|
+
version,
|
|
69
70
|
publishConfig: {
|
|
70
71
|
access: "public",
|
|
71
72
|
registry: registryUrl
|
|
@@ -135,7 +136,7 @@ async function publishPackage(input) {
|
|
|
135
136
|
const baseVersion = input.version ?? "1.0.0";
|
|
136
137
|
const version = input.unique ? `${baseVersion}-t${Date.now()}` : baseVersion;
|
|
137
138
|
const { token } = await obtainLegacyToken(input.registryUrl);
|
|
138
|
-
const tempFolder = await createTempProject(input.pkgName, version, input.registryUrl, token, input.dependencies ?? {}, input.devDependencies ?? {});
|
|
139
|
+
const tempFolder = await createTempProject(input.pkgName, version, input.registryUrl, token, input.dependencies ?? {}, input.devDependencies ?? {}, input.manifest ?? {});
|
|
139
140
|
const { stdout, stderr, exitCode } = await spawnNpmPublish(tempFolder, input.registryUrl);
|
|
140
141
|
if (exitCode !== 0) throw new Error(`[publishPackage] npm publish failed for ${input.pkgName}@${version} (exit ${exitCode}):\n${stderr || stdout}`);
|
|
141
142
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.cjs","names":[],"sources":["../../../src/tasks/publish.ts"],"sourcesContent":["import { spawn } from 'child_process';\nimport { mkdtemp, rm, writeFile } from 'fs/promises';\nimport { tmpdir } from 'os';\nimport { join } from 'path';\n\nexport interface PublishPackageInput {\n pkgName: string;\n version?: string;\n registryUrl: string;\n credentials: { user: string; password: string };\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n /**\n * When true, append a timestamp suffix to the version so reruns against\n * a persistent registry don't collide on 403. The suffix is a valid\n * semver prerelease, e.g. `1.0.0-t1712345678`. Defaults to false.\n */\n unique?: boolean;\n}\n\n/**\n * Shape of the argument passed to `cy.task('publishPackage', ...)`.\n *\n * `registryUrl` and `credentials` are optional here (they fall back to\n * whatever was configured in `setupVerdaccioTasks`), but `pkgName` is\n * still required — every publish needs a name.\n */\nexport type PublishPackageTaskInput = Partial<Omit<PublishPackageInput, 'pkgName'>> & {\n pkgName: string;\n};\n\nexport interface PublishPackageResult {\n pkgName: string;\n version: string;\n tempFolder: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nfunction sanitizeFolderName(name: string): string {\n return name.replace(/[^a-zA-Z0-9-_]/g, '-');\n}\n\n/**\n * Obtain a registry-API-compatible (legacy) auth token for publish.\n *\n * Strategy: create a throwaway user per call. `PUT /-/user/org.couchdb.user:<name>`\n * only returns a token on CREATE (and 409s on existing users), so we\n * guarantee success by generating a unique username each time.\n *\n * We need a legacy token specifically because:\n * - Verdaccio's default API middleware accepts legacy tokens but NOT\n * JWTs from `/-/verdaccio/sec/login`\n * - Modern npm (>= 10.x) refuses to run `npm publish` at all without\n * an `_authToken` entry in `.npmrc`, even against a registry that\n * allows `$anonymous` publish — it errors out client-side\n *\n * The throwaway user stays in the test registry's htpasswd store after\n * the run, which is fine for ephemeral CI environments and local temp\n * setups (both wipe storage between runs).\n */\nasync function obtainLegacyToken(registryUrl: string): Promise<{ user: string; token: string }> {\n const user = `e2e-bot-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n const password = 'e2e-bot-password';\n const base = registryUrl.replace(/\\/$/, '');\n const url = `${base}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;\n const res = await fetch(url, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: user,\n password,\n _id: `org.couchdb.user:${user}`,\n type: 'user',\n roles: [],\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `[publishPackage] failed to create throwaway user \"${user}\" ` +\n `(HTTP ${res.status}): ${body}`\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json.token) {\n throw new Error(\n `[publishPackage] user creation response did not contain a token: ${JSON.stringify(json)}`\n );\n }\n return { user, token: json.token };\n}\n\nasync function createTempProject(\n pkgName: string,\n version: string,\n registryUrl: string,\n token: string,\n dependencies: Record<string, string>,\n devDependencies: Record<string, string>\n): Promise<string> {\n const tempFolder = await mkdtemp(\n join(tmpdir(), `verdaccio-e2e-ui-${sanitizeFolderName(pkgName)}-`)\n );\n const manifest = {\n name: pkgName,\n version,\n description: `e2e test fixture ${pkgName}`,\n main: 'index.js',\n dependencies,\n devDependencies,\n keywords: ['verdaccio', 'e2e', 'test'],\n author: 'Verdaccio E2E <verdaccio@example.org>',\n license: 'MIT',\n // Scoped packages default to `restricted` access which modern npm\n // refuses to publish anonymously. Pin it to `public` so the CLI\n // skips that check for fixtures like `@verdaccio/pkg-scoped`.\n publishConfig: {\n access: 'public',\n registry: registryUrl,\n },\n };\n await writeFile(join(tempFolder, 'package.json'), JSON.stringify(manifest, null, 2));\n await writeFile(\n join(tempFolder, 'README.md'),\n `# ${pkgName}\\n\\nPublished by @verdaccio/e2e-ui for e2e testing.\\n`\n );\n await writeFile(join(tempFolder, 'index.js'), `module.exports = ${JSON.stringify(pkgName)};\\n`);\n\n // `.npmrc` — include an `_authToken` scoped to the registry host so\n // modern npm (>= 10.x) is willing to run `npm publish`. Without this\n // the CLI errors client-side with \"This command requires you to be\n // logged in.\" even against a registry configured for `$anonymous`\n // publish. The token is a legacy Verdaccio auth token obtained by\n // creating a throwaway user via `PUT /-/user/...`.\n const registryHost = registryUrl.replace(/^https?:/, '');\n const npmrc = [\n `registry=${registryUrl}`,\n `${registryHost}/:_authToken=${token}`,\n // Force public access for scoped packages.\n 'access=public',\n '',\n ].join('\\n');\n await writeFile(join(tempFolder, '.npmrc'), npmrc);\n\n return tempFolder;\n}\n\nfunction spawnNpmPublish(\n cwd: string,\n registryUrl: string\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n return new Promise((resolvePromise, rejectPromise) => {\n // `--tag latest` is required so npm accepts prerelease versions\n // (e.g. `1.0.0-t<ts>` when `unique: true`). Without it npm bails\n // with \"You must specify a tag using --tag when publishing a\n // prerelease version.\" For non-prerelease versions it's a no-op.\n const proc = spawn(\n 'npm',\n ['publish', '--registry', registryUrl, '--tag', 'latest', '--loglevel=error'],\n {\n cwd,\n env: { ...process.env },\n }\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr.on('data', (chunk) => {\n stderr += chunk.toString();\n });\n proc.on('error', rejectPromise);\n proc.on('close', (code) => {\n resolvePromise({ stdout, stderr, exitCode: code ?? -1 });\n });\n });\n}\n\n/**\n * Publish a throwaway npm package to the target Verdaccio registry.\n *\n * Flow:\n * 1. Create a throwaway user via `PUT /-/user/...` and capture its\n * legacy auth token. See `obtainLegacyToken` for why.\n * 2. Scaffold a temp project with `package.json`, `README.md`,\n * `index.js`, and an `.npmrc` that includes the token.\n * 3. Spawn `npm publish` from that temp dir.\n *\n * `input.credentials` is kept on the signature for forward-compat but\n * is currently unused — each call mints its own throwaway user.\n *\n * Throws on non-zero npm exit. Returns the temp folder path on success\n * so callers can inspect or clean up.\n */\nexport async function publishPackage(input: PublishPackageInput): Promise<PublishPackageResult> {\n const baseVersion = input.version ?? '1.0.0';\n const version = input.unique ? `${baseVersion}-t${Date.now()}` : baseVersion;\n\n const { token } = await obtainLegacyToken(input.registryUrl);\n\n const tempFolder = await createTempProject(\n input.pkgName,\n version,\n input.registryUrl,\n token,\n input.dependencies ?? {},\n input.devDependencies ?? {}\n );\n const { stdout, stderr, exitCode } = await spawnNpmPublish(tempFolder, input.registryUrl);\n if (exitCode !== 0) {\n throw new Error(\n `[publishPackage] npm publish failed for ${input.pkgName}@${version} ` +\n `(exit ${exitCode}):\\n${stderr || stdout}`\n );\n }\n return { pkgName: input.pkgName, version, tempFolder, stdout, stderr, exitCode };\n}\n\n/**\n * Remove a temp project folder previously created by publishPackage.\n * Safe to call with a missing path or a path outside the OS tmp dir —\n * in the latter case it refuses rather than rm-rf'ing arbitrary paths.\n */\nexport async function cleanupPublished(tempFolder: string): Promise<void> {\n if (!tempFolder) return;\n const tmpRoot = tmpdir();\n if (!tempFolder.startsWith(tmpRoot)) {\n throw new Error(`[cleanupPublished] refusing to remove \"${tempFolder}\" — not under ${tmpRoot}`);\n }\n await rm(tempFolder, { recursive: true, force: true });\n}\n\nexport interface UnpublishPackageInput {\n registryUrl: string;\n pkgName: string;\n /**\n * Temp folder returned by a previous `publishPackage` call. If\n * provided, its existing `.npmrc` (which already carries a legacy\n * auth token) is reused for the unpublish call — no extra throwaway\n * user is minted. Otherwise this task creates its own.\n */\n tempFolder?: string;\n}\n\nexport interface UnpublishPackageResult {\n pkgName: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n /**\n * True if the package was already absent (HTTP 404 from the\n * registry). Tests typically want to treat this as success.\n */\n alreadyGone: boolean;\n}\n\nfunction spawnNpmUnpublish(\n cwd: string,\n registryUrl: string,\n pkgSpec: string\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n return new Promise((resolvePromise, rejectPromise) => {\n const proc = spawn(\n 'npm',\n ['unpublish', pkgSpec, '--force', '--registry', registryUrl, '--loglevel=error'],\n {\n cwd,\n env: { ...process.env },\n }\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr.on('data', (chunk) => {\n stderr += chunk.toString();\n });\n proc.on('error', rejectPromise);\n proc.on('close', (code) => {\n resolvePromise({ stdout, stderr, exitCode: code ?? -1 });\n });\n });\n}\n\n/**\n * Unpublish a package from the target registry so subsequent tests\n * start from a clean slate.\n *\n * If `tempFolder` is provided (typically from a prior `publishPackage`\n * result), its `.npmrc` is reused so no extra throwaway user is\n * minted. Otherwise this function creates its own temp folder + token.\n * Either way the temp folder used by THIS call is removed on exit\n * (but callers still own the lifecycle of a tempFolder they passed in).\n *\n * Treats HTTP 404 / \"tarball does not exist\" as success so reruns and\n * parallel teardowns don't flap.\n */\nexport async function unpublishPackage(\n input: UnpublishPackageInput\n): Promise<UnpublishPackageResult> {\n let workingFolder = input.tempFolder;\n let ownsWorkingFolder = false;\n\n if (!workingFolder) {\n ownsWorkingFolder = true;\n const { token } = await obtainLegacyToken(input.registryUrl);\n workingFolder = await mkdtemp(join(tmpdir(), `verdaccio-e2e-ui-unpublish-`));\n const registryHost = input.registryUrl.replace(/^https?:/, '');\n await writeFile(\n join(workingFolder, '.npmrc'),\n [`registry=${input.registryUrl}`, `${registryHost}/:_authToken=${token}`, ''].join('\\n')\n );\n }\n\n try {\n const { stdout, stderr, exitCode } = await spawnNpmUnpublish(\n workingFolder,\n input.registryUrl,\n input.pkgName\n );\n\n // Treat \"already absent\" as success. npm prints slightly different\n // messages depending on version — match loosely.\n const alreadyGone = /404|not found|no such package|does not (exist|match)/i.test(\n `${stderr}\\n${stdout}`\n );\n\n if (exitCode !== 0 && !alreadyGone) {\n throw new Error(\n `[unpublishPackage] npm unpublish failed for ${input.pkgName} ` +\n `(exit ${exitCode}):\\n${stderr || stdout}`\n );\n }\n\n return {\n pkgName: input.pkgName,\n stdout,\n stderr,\n exitCode,\n alreadyGone,\n };\n } finally {\n if (ownsWorkingFolder && workingFolder) {\n await rm(workingFolder, { recursive: true, force: true }).catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publish.cjs","names":[],"sources":["../../../src/tasks/publish.ts"],"sourcesContent":["import { spawn } from 'child_process';\nimport { mkdtemp, rm, writeFile } from 'fs/promises';\nimport { tmpdir } from 'os';\nimport { join } from 'path';\n\nexport interface PublishPackageInput {\n pkgName: string;\n version?: string;\n registryUrl: string;\n credentials: { user: string; password: string };\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n /**\n * Extra fields merged into the generated package.json AFTER the\n * defaults, so fixtures can carry arbitrary manifest shapes: a string\n * `repository`, a `funding` array, `contributors` without email, a\n * non-string `homepage`… — exactly the field forms npm accepts on\n * publish but UIs tend to mishandle. `name`, `version` and\n * `publishConfig` always win over this override.\n */\n manifest?: Record<string, unknown>;\n /**\n * When true, append a timestamp suffix to the version so reruns against\n * a persistent registry don't collide on 403. The suffix is a valid\n * semver prerelease, e.g. `1.0.0-t1712345678`. Defaults to false.\n */\n unique?: boolean;\n}\n\n/**\n * Shape of the argument passed to `cy.task('publishPackage', ...)`.\n *\n * `registryUrl` and `credentials` are optional here (they fall back to\n * whatever was configured in `setupVerdaccioTasks`), but `pkgName` is\n * still required — every publish needs a name.\n */\nexport type PublishPackageTaskInput = Partial<Omit<PublishPackageInput, 'pkgName'>> & {\n pkgName: string;\n};\n\nexport interface PublishPackageResult {\n pkgName: string;\n version: string;\n tempFolder: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nfunction sanitizeFolderName(name: string): string {\n return name.replace(/[^a-zA-Z0-9-_]/g, '-');\n}\n\n/**\n * Obtain a registry-API-compatible (legacy) auth token for publish.\n *\n * Strategy: create a throwaway user per call. `PUT /-/user/org.couchdb.user:<name>`\n * only returns a token on CREATE (and 409s on existing users), so we\n * guarantee success by generating a unique username each time.\n *\n * We need a legacy token specifically because:\n * - Verdaccio's default API middleware accepts legacy tokens but NOT\n * JWTs from `/-/verdaccio/sec/login`\n * - Modern npm (>= 10.x) refuses to run `npm publish` at all without\n * an `_authToken` entry in `.npmrc`, even against a registry that\n * allows `$anonymous` publish — it errors out client-side\n *\n * The throwaway user stays in the test registry's htpasswd store after\n * the run, which is fine for ephemeral CI environments and local temp\n * setups (both wipe storage between runs).\n */\nasync function obtainLegacyToken(registryUrl: string): Promise<{ user: string; token: string }> {\n const user = `e2e-bot-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n const password = 'e2e-bot-password';\n const base = registryUrl.replace(/\\/$/, '');\n const url = `${base}/-/user/org.couchdb.user:${encodeURIComponent(user)}`;\n const res = await fetch(url, {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n name: user,\n password,\n _id: `org.couchdb.user:${user}`,\n type: 'user',\n roles: [],\n }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `[publishPackage] failed to create throwaway user \"${user}\" ` +\n `(HTTP ${res.status}): ${body}`\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json.token) {\n throw new Error(\n `[publishPackage] user creation response did not contain a token: ${JSON.stringify(json)}`\n );\n }\n return { user, token: json.token };\n}\n\nasync function createTempProject(\n pkgName: string,\n version: string,\n registryUrl: string,\n token: string,\n dependencies: Record<string, string>,\n devDependencies: Record<string, string>,\n manifestOverrides: Record<string, unknown> = {}\n): Promise<string> {\n const tempFolder = await mkdtemp(\n join(tmpdir(), `verdaccio-e2e-ui-${sanitizeFolderName(pkgName)}-`)\n );\n const manifest = {\n description: `e2e test fixture ${pkgName}`,\n main: 'index.js',\n dependencies,\n devDependencies,\n keywords: ['verdaccio', 'e2e', 'test'],\n author: 'Verdaccio E2E <verdaccio@example.org>',\n license: 'MIT',\n // Fixture-specific manifest shapes (string `repository`, `funding`\n // arrays, contributors without email…) — npm accepts them on\n // publish, so the UI must render them. Identity fields and\n // publishConfig below always win over the overrides.\n ...manifestOverrides,\n name: pkgName,\n version,\n // Scoped packages default to `restricted` access which modern npm\n // refuses to publish anonymously. Pin it to `public` so the CLI\n // skips that check for fixtures like `@verdaccio/pkg-scoped`.\n publishConfig: {\n access: 'public',\n registry: registryUrl,\n },\n };\n await writeFile(join(tempFolder, 'package.json'), JSON.stringify(manifest, null, 2));\n await writeFile(\n join(tempFolder, 'README.md'),\n `# ${pkgName}\\n\\nPublished by @verdaccio/e2e-ui for e2e testing.\\n`\n );\n await writeFile(join(tempFolder, 'index.js'), `module.exports = ${JSON.stringify(pkgName)};\\n`);\n\n // `.npmrc` — include an `_authToken` scoped to the registry host so\n // modern npm (>= 10.x) is willing to run `npm publish`. Without this\n // the CLI errors client-side with \"This command requires you to be\n // logged in.\" even against a registry configured for `$anonymous`\n // publish. The token is a legacy Verdaccio auth token obtained by\n // creating a throwaway user via `PUT /-/user/...`.\n const registryHost = registryUrl.replace(/^https?:/, '');\n const npmrc = [\n `registry=${registryUrl}`,\n `${registryHost}/:_authToken=${token}`,\n // Force public access for scoped packages.\n 'access=public',\n '',\n ].join('\\n');\n await writeFile(join(tempFolder, '.npmrc'), npmrc);\n\n return tempFolder;\n}\n\nfunction spawnNpmPublish(\n cwd: string,\n registryUrl: string\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n return new Promise((resolvePromise, rejectPromise) => {\n // `--tag latest` is required so npm accepts prerelease versions\n // (e.g. `1.0.0-t<ts>` when `unique: true`). Without it npm bails\n // with \"You must specify a tag using --tag when publishing a\n // prerelease version.\" For non-prerelease versions it's a no-op.\n const proc = spawn(\n 'npm',\n ['publish', '--registry', registryUrl, '--tag', 'latest', '--loglevel=error'],\n {\n cwd,\n env: { ...process.env },\n }\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr.on('data', (chunk) => {\n stderr += chunk.toString();\n });\n proc.on('error', rejectPromise);\n proc.on('close', (code) => {\n resolvePromise({ stdout, stderr, exitCode: code ?? -1 });\n });\n });\n}\n\n/**\n * Publish a throwaway npm package to the target Verdaccio registry.\n *\n * Flow:\n * 1. Create a throwaway user via `PUT /-/user/...` and capture its\n * legacy auth token. See `obtainLegacyToken` for why.\n * 2. Scaffold a temp project with `package.json`, `README.md`,\n * `index.js`, and an `.npmrc` that includes the token.\n * 3. Spawn `npm publish` from that temp dir.\n *\n * `input.credentials` is kept on the signature for forward-compat but\n * is currently unused — each call mints its own throwaway user.\n *\n * Throws on non-zero npm exit. Returns the temp folder path on success\n * so callers can inspect or clean up.\n */\nexport async function publishPackage(input: PublishPackageInput): Promise<PublishPackageResult> {\n const baseVersion = input.version ?? '1.0.0';\n const version = input.unique ? `${baseVersion}-t${Date.now()}` : baseVersion;\n\n const { token } = await obtainLegacyToken(input.registryUrl);\n\n const tempFolder = await createTempProject(\n input.pkgName,\n version,\n input.registryUrl,\n token,\n input.dependencies ?? {},\n input.devDependencies ?? {},\n input.manifest ?? {}\n );\n const { stdout, stderr, exitCode } = await spawnNpmPublish(tempFolder, input.registryUrl);\n if (exitCode !== 0) {\n throw new Error(\n `[publishPackage] npm publish failed for ${input.pkgName}@${version} ` +\n `(exit ${exitCode}):\\n${stderr || stdout}`\n );\n }\n return { pkgName: input.pkgName, version, tempFolder, stdout, stderr, exitCode };\n}\n\n/**\n * Remove a temp project folder previously created by publishPackage.\n * Safe to call with a missing path or a path outside the OS tmp dir —\n * in the latter case it refuses rather than rm-rf'ing arbitrary paths.\n */\nexport async function cleanupPublished(tempFolder: string): Promise<void> {\n if (!tempFolder) return;\n const tmpRoot = tmpdir();\n if (!tempFolder.startsWith(tmpRoot)) {\n throw new Error(`[cleanupPublished] refusing to remove \"${tempFolder}\" — not under ${tmpRoot}`);\n }\n await rm(tempFolder, { recursive: true, force: true });\n}\n\nexport interface UnpublishPackageInput {\n registryUrl: string;\n pkgName: string;\n /**\n * Temp folder returned by a previous `publishPackage` call. If\n * provided, its existing `.npmrc` (which already carries a legacy\n * auth token) is reused for the unpublish call — no extra throwaway\n * user is minted. Otherwise this task creates its own.\n */\n tempFolder?: string;\n}\n\nexport interface UnpublishPackageResult {\n pkgName: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n /**\n * True if the package was already absent (HTTP 404 from the\n * registry). Tests typically want to treat this as success.\n */\n alreadyGone: boolean;\n}\n\nfunction spawnNpmUnpublish(\n cwd: string,\n registryUrl: string,\n pkgSpec: string\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n return new Promise((resolvePromise, rejectPromise) => {\n const proc = spawn(\n 'npm',\n ['unpublish', pkgSpec, '--force', '--registry', registryUrl, '--loglevel=error'],\n {\n cwd,\n env: { ...process.env },\n }\n );\n let stdout = '';\n let stderr = '';\n proc.stdout.on('data', (chunk) => {\n stdout += chunk.toString();\n });\n proc.stderr.on('data', (chunk) => {\n stderr += chunk.toString();\n });\n proc.on('error', rejectPromise);\n proc.on('close', (code) => {\n resolvePromise({ stdout, stderr, exitCode: code ?? -1 });\n });\n });\n}\n\n/**\n * Unpublish a package from the target registry so subsequent tests\n * start from a clean slate.\n *\n * If `tempFolder` is provided (typically from a prior `publishPackage`\n * result), its `.npmrc` is reused so no extra throwaway user is\n * minted. Otherwise this function creates its own temp folder + token.\n * Either way the temp folder used by THIS call is removed on exit\n * (but callers still own the lifecycle of a tempFolder they passed in).\n *\n * Treats HTTP 404 / \"tarball does not exist\" as success so reruns and\n * parallel teardowns don't flap.\n */\nexport async function unpublishPackage(\n input: UnpublishPackageInput\n): Promise<UnpublishPackageResult> {\n let workingFolder = input.tempFolder;\n let ownsWorkingFolder = false;\n\n if (!workingFolder) {\n ownsWorkingFolder = true;\n const { token } = await obtainLegacyToken(input.registryUrl);\n workingFolder = await mkdtemp(join(tmpdir(), `verdaccio-e2e-ui-unpublish-`));\n const registryHost = input.registryUrl.replace(/^https?:/, '');\n await writeFile(\n join(workingFolder, '.npmrc'),\n [`registry=${input.registryUrl}`, `${registryHost}/:_authToken=${token}`, ''].join('\\n')\n );\n }\n\n try {\n const { stdout, stderr, exitCode } = await spawnNpmUnpublish(\n workingFolder,\n input.registryUrl,\n input.pkgName\n );\n\n // Treat \"already absent\" as success. npm prints slightly different\n // messages depending on version — match loosely.\n const alreadyGone = /404|not found|no such package|does not (exist|match)/i.test(\n `${stderr}\\n${stdout}`\n );\n\n if (exitCode !== 0 && !alreadyGone) {\n throw new Error(\n `[unpublishPackage] npm unpublish failed for ${input.pkgName} ` +\n `(exit ${exitCode}):\\n${stderr || stdout}`\n );\n }\n\n return {\n pkgName: input.pkgName,\n stdout,\n stderr,\n exitCode,\n alreadyGone,\n };\n } finally {\n if (ownsWorkingFolder && workingFolder) {\n await rm(workingFolder, { recursive: true, force: true }).catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -31,7 +31,6 @@ function changePasswordTests(config) {
|
|
|
31
31
|
const { header, login } = config.testIds;
|
|
32
32
|
const { loginDialog } = config.selectors;
|
|
33
33
|
const { features } = config;
|
|
34
|
-
const GENERIC_FAILURE_TEXT = "Failed to change password";
|
|
35
34
|
describe("change password", () => {
|
|
36
35
|
const CHANGE_PASSWORD_PATH = "/-/web/change-password";
|
|
37
36
|
const { user, password } = config.credentials;
|
|
@@ -110,7 +109,7 @@ function changePasswordTests(config) {
|
|
|
110
109
|
cy.get("input[name=\"confirmPassword\"]").type("newSecretPass123");
|
|
111
110
|
cy.get("form button[type=\"submit\"]").should("not.be.disabled").click();
|
|
112
111
|
cy.wait("@reset").its("response.statusCode").should("not.eq", 200);
|
|
113
|
-
cy.getByTestId(login.error, { timeout: 5e3 }).should("be.visible").and("contain.text",
|
|
112
|
+
cy.getByTestId(login.error, { timeout: 5e3 }).should("be.visible").and("not.contain.text", "Cannot PUT").invoke("text").then((text) => expect(text.trim()).to.not.be.empty);
|
|
114
113
|
cy.location("pathname").should("include", CHANGE_PASSWORD_PATH);
|
|
115
114
|
});
|
|
116
115
|
require_features.maybeIt(features.changePassword.happyPath)("should change the password and navigate to the success page", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-password.cjs","names":[],"sources":["../../../src/tests/change-password.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the Change Password page at /-/web/change-password.\n *\n * The page renders only when the server is started with\n * `flags.changePassword: true` (otherwise the React component\n * redirects to `/` on mount). Each test logs in first, navigates\n * directly to the page, and drives the form.\n *\n * Selector strategy: the ChangePassword form does not ship stable\n * `id`/testid attributes on its inputs, but every field is registered\n * via react-hook-form's `register('<name>')`, which sets a stable\n * `name` attribute on the underlying `<input>`. The labels themselves\n * are `t('security.changePassword.*')` calls — when the i18n bundle\n * hasn't finished loading (or isn't loaded at all on this route), MUI\n * renders the literal i18n key as the label, so any selector that\n * matches on visible label text silently misses every field and the\n * form stays empty (which would also make the mismatch test \"pass\"\n * for the wrong reason: the submit button is disabled because the\n * form is empty, not because yup rejected the mismatch).\n *\n * The stable, i18n-independent contract from ChangePassword.tsx is:\n * register('username') → input[name=\"username\"]\n * register('oldPassword') → input[name=\"oldPassword\"]\n * register('newPassword') → input[name=\"newPassword\"]\n * register('confirmPassword') → input[name=\"confirmPassword\"]\n * submit button → form button[type=\"submit\"]\n */\nexport function changePasswordTests(config: RegistryConfig) {\n const { header, login } = config.testIds;\n const { loginDialog } = config.selectors;\n const { features } = config;\n\n // The onSubmit catch block in ChangePassword.tsx sets a hardcoded\n // English string — if the upstream component ever localizes this,\n // this constant and the wrongOldPassword test will need updating.\n const GENERIC_FAILURE_TEXT = 'Failed to change password';\n\n describe('change password', () => {\n const CHANGE_PASSWORD_PATH = '/-/web/change-password';\n const { user, password } = config.credentials;\n\n /**\n * Tests mutate the user's password. We track the \"current\" value\n * across tests so the `after()` hook can restore the original,\n * leaving the registry in the same state other suites assume.\n */\n let currentPassword = password;\n\n /**\n * Capability check. The ChangePassword page's `useEffect` redirects\n * to `/` whenever `configuration.flags.changePassword` is not truthy\n * — which is the case on any registry that either (a) didn't set\n * `flags.changePassword: true` in its config, or (b) runs a\n * verdaccio build whose middleware doesn't yet propagate the flag\n * into `__VERDACCIO_BASENAME_UI_OPTIONS` (older `verdaccio:6`\n * tagged images fall in this bucket).\n *\n * In either case the suite has nothing to exercise, so skip the\n * whole describe with a clear reason rather than burning five\n * seconds per test on cy.contains timeouts.\n */\n before(function () {\n cy.visit(config.registryUrl);\n cy.window().then((win) => {\n const opts = (win as any).__VERDACCIO_BASENAME_UI_OPTIONS;\n const enabled = !!opts?.flags?.changePassword;\n if (!enabled) {\n // eslint-disable-next-line no-console\n console.warn(\n '[change-password] server did not advertise flags.changePassword=true ' +\n '— skipping suite. ui-options.flags: ' +\n JSON.stringify(opts?.flags ?? {})\n );\n this.skip();\n }\n });\n });\n\n beforeEach(() => {\n // Intercept the login POST and wait on it explicitly instead of\n // leaning on a visual sentinel — mirrors the pattern used by\n // signinTests and avoids a race between cy.login's fire-and-forget\n // submit and the next cy.visit.\n cy.intercept('POST', '/-/verdaccio/sec/login').as('signChangePwd');\n cy.visit(config.registryUrl);\n cy.login(user, currentPassword, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@signChangePwd').its('response.statusCode').should('eq', 200);\n\n cy.visit(CHANGE_PASSWORD_PATH);\n // If flags.changePassword is off server-side, the component's\n // useEffect redirects to `/` and this assertion times out — which\n // is the correct signal that the registry is misconfigured.\n // Use the stable `type=\"submit\"` selector so the assertion is\n // independent of whether the i18n bundle has resolved by now.\n cy.get('form button[type=\"submit\"]', { timeout: 5000 }).should('be.visible');\n });\n\n after(() => {\n // Restore the original password so subsequent spec files\n // (and retries) can still log in with `config.credentials`.\n if (currentPassword === password) return;\n cy.intercept('POST', '/-/verdaccio/sec/login').as('signChangePwdRestore');\n cy.visit(config.registryUrl);\n cy.login(user, currentPassword, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@signChangePwdRestore').its('response.statusCode').should('eq', 200);\n cy.visit(CHANGE_PASSWORD_PATH);\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type(password);\n cy.get('input[name=\"confirmPassword\"]').type(password);\n cy.get('form button[type=\"submit\"]').click();\n currentPassword = password;\n });\n\n // ── Validation (client-side yup) ─────────────────────────────\n\n maybeIt(features.changePassword.validation)(\n 'should disable the submit button while the form is empty',\n () => {\n cy.get('form button[type=\"submit\"]').should('be.disabled');\n }\n );\n\n maybeIt(features.changePassword.validation)(\n 'should keep submit disabled when new and confirm passwords mismatch',\n () => {\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type('newSecretPass123');\n cy.get('input[name=\"confirmPassword\"]').type('different-value');\n // yup schema rejects mismatch → isValid stays false → button disabled.\n cy.get('form button[type=\"submit\"]').should('be.disabled');\n }\n );\n\n // ── Server error path ────────────────────────────────────────\n\n maybeIt(features.changePassword.wrongOldPassword)(\n 'should show an error banner when the old password is wrong',\n () => {\n cy.intercept('PUT', '/-/verdaccio/sec/reset_password').as('reset');\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type('definitely-wrong-xyz');\n cy.get('input[name=\"newPassword\"]').type('newSecretPass123');\n cy.get('input[name=\"confirmPassword\"]').type('newSecretPass123');\n cy.get('form button[type=\"submit\"]').should('not.be.disabled').click();\n // Server rejects (htpasswd → plain Error → handler returns 4xx).\n cy.wait('@reset').its('response.statusCode').should('not.eq', 200);\n // onSubmit's catch sets errors.root → rendered via LoginDialogFormError.\n cy.getByTestId(login.error, { timeout: 5000 })\n .should('be.visible')\n .and('contain.text', GENERIC_FAILURE_TEXT);\n // Still on the change-password page so the user can retry.\n cy.location('pathname').should('include', CHANGE_PASSWORD_PATH);\n }\n );\n\n // ── Happy path (mutates state; keeps `currentPassword` in sync) ─\n\n maybeIt(features.changePassword.happyPath)(\n 'should change the password and navigate to the success page',\n () => {\n const newPassword = `${currentPassword}-rotated`;\n cy.intercept('PUT', '/-/verdaccio/sec/reset_password').as('reset');\n\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type(newPassword);\n cy.get('input[name=\"confirmPassword\"]').type(newPassword);\n cy.get('form button[type=\"submit\"]').should('not.be.disabled').click();\n\n cy.wait('@reset').its('response.statusCode').should('eq', 200);\n // Post-submit the component navigates to Route.SUCCESS with a\n // messageType query param. Assert the pathname; the message text\n // is i18n-driven and out of scope for this selector layer.\n cy.location('pathname', { timeout: 5000 }).should('include', '/-/web/success');\n\n // Track the rotation so `after()` can restore it.\n currentPassword = newPassword;\n }\n );\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"change-password.cjs","names":[],"sources":["../../../src/tests/change-password.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the Change Password page at /-/web/change-password.\n *\n * The page renders only when the server is started with\n * `flags.changePassword: true` (otherwise the React component\n * redirects to `/` on mount). Each test logs in first, navigates\n * directly to the page, and drives the form.\n *\n * Selector strategy: the ChangePassword form does not ship stable\n * `id`/testid attributes on its inputs, but every field is registered\n * via react-hook-form's `register('<name>')`, which sets a stable\n * `name` attribute on the underlying `<input>`. The labels themselves\n * are `t('security.changePassword.*')` calls — when the i18n bundle\n * hasn't finished loading (or isn't loaded at all on this route), MUI\n * renders the literal i18n key as the label, so any selector that\n * matches on visible label text silently misses every field and the\n * form stays empty (which would also make the mismatch test \"pass\"\n * for the wrong reason: the submit button is disabled because the\n * form is empty, not because yup rejected the mismatch).\n *\n * The stable, i18n-independent contract from ChangePassword.tsx is:\n * register('username') → input[name=\"username\"]\n * register('oldPassword') → input[name=\"oldPassword\"]\n * register('newPassword') → input[name=\"newPassword\"]\n * register('confirmPassword') → input[name=\"confirmPassword\"]\n * submit button → form button[type=\"submit\"]\n */\nexport function changePasswordTests(config: RegistryConfig) {\n const { header, login } = config.testIds;\n const { loginDialog } = config.selectors;\n const { features } = config;\n\n // The wrongOldPassword banner text differs across verdaccio lines: older\n // published ui-theme shows a hardcoded \"Failed to change password\", while\n // current master surfaces the server message (\"internal server error\") or\n // the i18n fallback (\"Unable to change password\") via authErrorMessage. The\n // contract this test pins is \"a non-empty error banner appears, not\n // silence\", so it asserts visibility + non-empty text rather than an exact\n // string. The Express \"Cannot PUT\" 404 body is the one text that must never\n // appear — that means the reset_password route was not registered (the\n // server was started without flags.changePassword).\n\n describe('change password', () => {\n const CHANGE_PASSWORD_PATH = '/-/web/change-password';\n const { user, password } = config.credentials;\n\n /**\n * Tests mutate the user's password. We track the \"current\" value\n * across tests so the `after()` hook can restore the original,\n * leaving the registry in the same state other suites assume.\n */\n let currentPassword = password;\n\n /**\n * Capability check. The ChangePassword page's `useEffect` redirects\n * to `/` whenever `configuration.flags.changePassword` is not truthy\n * — which is the case on any registry that either (a) didn't set\n * `flags.changePassword: true` in its config, or (b) runs a\n * verdaccio build whose middleware doesn't yet propagate the flag\n * into `__VERDACCIO_BASENAME_UI_OPTIONS` (older `verdaccio:6`\n * tagged images fall in this bucket).\n *\n * In either case the suite has nothing to exercise, so skip the\n * whole describe with a clear reason rather than burning five\n * seconds per test on cy.contains timeouts.\n */\n before(function () {\n cy.visit(config.registryUrl);\n cy.window().then((win) => {\n const opts = (win as any).__VERDACCIO_BASENAME_UI_OPTIONS;\n const enabled = !!opts?.flags?.changePassword;\n if (!enabled) {\n // eslint-disable-next-line no-console\n console.warn(\n '[change-password] server did not advertise flags.changePassword=true ' +\n '— skipping suite. ui-options.flags: ' +\n JSON.stringify(opts?.flags ?? {})\n );\n this.skip();\n }\n });\n });\n\n beforeEach(() => {\n // Intercept the login POST and wait on it explicitly instead of\n // leaning on a visual sentinel — mirrors the pattern used by\n // signinTests and avoids a race between cy.login's fire-and-forget\n // submit and the next cy.visit.\n cy.intercept('POST', '/-/verdaccio/sec/login').as('signChangePwd');\n cy.visit(config.registryUrl);\n cy.login(user, currentPassword, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@signChangePwd').its('response.statusCode').should('eq', 200);\n\n cy.visit(CHANGE_PASSWORD_PATH);\n // If flags.changePassword is off server-side, the component's\n // useEffect redirects to `/` and this assertion times out — which\n // is the correct signal that the registry is misconfigured.\n // Use the stable `type=\"submit\"` selector so the assertion is\n // independent of whether the i18n bundle has resolved by now.\n cy.get('form button[type=\"submit\"]', { timeout: 5000 }).should('be.visible');\n });\n\n after(() => {\n // Restore the original password so subsequent spec files\n // (and retries) can still log in with `config.credentials`.\n if (currentPassword === password) return;\n cy.intercept('POST', '/-/verdaccio/sec/login').as('signChangePwdRestore');\n cy.visit(config.registryUrl);\n cy.login(user, currentPassword, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@signChangePwdRestore').its('response.statusCode').should('eq', 200);\n cy.visit(CHANGE_PASSWORD_PATH);\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type(password);\n cy.get('input[name=\"confirmPassword\"]').type(password);\n cy.get('form button[type=\"submit\"]').click();\n currentPassword = password;\n });\n\n // ── Validation (client-side yup) ─────────────────────────────\n\n maybeIt(features.changePassword.validation)(\n 'should disable the submit button while the form is empty',\n () => {\n cy.get('form button[type=\"submit\"]').should('be.disabled');\n }\n );\n\n maybeIt(features.changePassword.validation)(\n 'should keep submit disabled when new and confirm passwords mismatch',\n () => {\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type('newSecretPass123');\n cy.get('input[name=\"confirmPassword\"]').type('different-value');\n // yup schema rejects mismatch → isValid stays false → button disabled.\n cy.get('form button[type=\"submit\"]').should('be.disabled');\n }\n );\n\n // ── Server error path ────────────────────────────────────────\n\n maybeIt(features.changePassword.wrongOldPassword)(\n 'should show an error banner when the old password is wrong',\n () => {\n cy.intercept('PUT', '/-/verdaccio/sec/reset_password').as('reset');\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type('definitely-wrong-xyz');\n cy.get('input[name=\"newPassword\"]').type('newSecretPass123');\n cy.get('input[name=\"confirmPassword\"]').type('newSecretPass123');\n cy.get('form button[type=\"submit\"]').should('not.be.disabled').click();\n // Server rejects the wrong password (the route exists → not a 404).\n cy.wait('@reset').its('response.statusCode').should('not.eq', 200);\n // onSubmit's catch sets errors.root → rendered via LoginDialogFormError.\n // Assert a non-empty banner (not silence), text-agnostic across lines,\n // but never the raw \"Cannot PUT\" 404 (route missing).\n cy.getByTestId(login.error, { timeout: 5000 })\n .should('be.visible')\n .and('not.contain.text', 'Cannot PUT')\n .invoke('text')\n .then((text) => expect(text.trim()).to.not.be.empty);\n // Still on the change-password page so the user can retry.\n cy.location('pathname').should('include', CHANGE_PASSWORD_PATH);\n }\n );\n\n // ── Happy path (mutates state; keeps `currentPassword` in sync) ─\n\n maybeIt(features.changePassword.happyPath)(\n 'should change the password and navigate to the success page',\n () => {\n const newPassword = `${currentPassword}-rotated`;\n cy.intercept('PUT', '/-/verdaccio/sec/reset_password').as('reset');\n\n cy.get('input[name=\"username\"]').type(user);\n cy.get('input[name=\"oldPassword\"]').type(currentPassword);\n cy.get('input[name=\"newPassword\"]').type(newPassword);\n cy.get('input[name=\"confirmPassword\"]').type(newPassword);\n cy.get('form button[type=\"submit\"]').should('not.be.disabled').click();\n\n cy.wait('@reset').its('response.statusCode').should('eq', 200);\n // Post-submit the component navigates to Route.SUCCESS with a\n // messageType query param. Assert the pathname; the message text\n // is i18n-driven and out of scope for this selector layer.\n cy.location('pathname', { timeout: 5000 }).should('include', '/-/web/success');\n\n // Track the rotation so `after()` can restore it.\n currentPassword = newPassword;\n }\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -56,6 +56,39 @@ function detailTests(config) {
|
|
|
56
56
|
cy.getByTestId(pkg.sidebar).should("be.visible");
|
|
57
57
|
cy.getByTestId(pkg.sidebar).contains(version);
|
|
58
58
|
});
|
|
59
|
+
require_features.maybeIt(features.detail.staleVersionsNavigation)("the versions tab must show the CURRENT package after SPA navigation", () => {
|
|
60
|
+
const otherPkg = "@verdaccio/stale-nav-fixture";
|
|
61
|
+
let otherTemp = null;
|
|
62
|
+
cy.task("publishPackage", {
|
|
63
|
+
pkgName: otherPkg,
|
|
64
|
+
version: "2.0.0",
|
|
65
|
+
unique: true
|
|
66
|
+
}).then((result) => {
|
|
67
|
+
otherTemp = result?.tempFolder ?? null;
|
|
68
|
+
});
|
|
69
|
+
cy.task("publishPackage", {
|
|
70
|
+
pkgName,
|
|
71
|
+
version: "1.1.0",
|
|
72
|
+
unique: true
|
|
73
|
+
}).then((r) => {
|
|
74
|
+
if (r?.tempFolder) cy.task("cleanupPublished", r.tempFolder);
|
|
75
|
+
});
|
|
76
|
+
cy.visit(config.registryUrl);
|
|
77
|
+
cy.contains(`[data-testid="${pkg.title}"]`, pkgName).click();
|
|
78
|
+
cy.getByTestId(pkg.versionsTab, { timeout: 1e4 }).click();
|
|
79
|
+
cy.get("[data-testid=\"version-list-text\"]").should("have.length", 2);
|
|
80
|
+
cy.getByTestId(config.testIds.header.defaultLogo).first().click();
|
|
81
|
+
cy.contains(`[data-testid="${pkg.title}"]`, otherPkg).click();
|
|
82
|
+
cy.getByTestId(pkg.versionsTab, { timeout: 1e4 }).click();
|
|
83
|
+
cy.get("[data-testid=\"version-list-text\"]").should("have.length", 1);
|
|
84
|
+
cy.task("unpublishPackage", {
|
|
85
|
+
pkgName: otherPkg,
|
|
86
|
+
tempFolder: otherTemp ?? void 0
|
|
87
|
+
});
|
|
88
|
+
cy.then(() => {
|
|
89
|
+
if (otherTemp) cy.task("cleanupPublished", otherTemp);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
59
92
|
require_features.maybeIt(features.detail.versionNotFound)("should render Not Found for a version that does not exist", () => {
|
|
60
93
|
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`).as("sidebar");
|
|
61
94
|
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/9.9.9`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detail.cjs","names":[],"sources":["../../../src/tests/detail.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Package detail page tests: version-pinned URLs and the wire format of\n * the UI's data requests.\n *\n * The wire format is an observable contract, not an implementation\n * detail: proxies, intercepts and every npm client expect scoped names\n * as literal `/@scope/name`. A ui-components refactor that\n * percent-encoded the `@` broke exactly these suites\n * (verdaccio/verdaccio#6210), which is why it is pinned here on purpose.\n */\nexport function detailTests(config: RegistryConfig) {\n const { features } = config;\n const { package: pkg, home } = config.testIds;\n\n describe('package detail', () => {\n describe('with a published package', () => {\n const pkgName = '@verdaccio/detail-fixture';\n let tempFolder: string | null = null;\n // `unique: true` uniquifies the VERSION (1.0.0-t<ts>), not the name\n let version = '1.0.0';\n\n beforeEach(() => {\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n version = result?.version ?? '1.0.0';\n });\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.detail.scopedWireFormat)(\n 'should request sidebar and readme with the scope literal (@, not %40)',\n () => {\n // Literal-path intercepts: if the UI ever encodes `@` or `/`,\n // these never match and the waits below time out.\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}*`).as('readme');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(`/sidebar/${pkgName}`);\n expect(interception.request.url).to.not.contain('%40');\n });\n cy.wait('@readme', { timeout: 10000 });\n cy.getByTestId(pkg.sidebar).should('be.visible');\n }\n );\n\n maybeIt(features.detail.versionPinned)(\n 'should pin the requested version via the ?v= query on /v/<version> urls',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/${version}`);\n\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(`v=${version}`);\n });\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.sidebar).contains(version);\n }\n );\n\n maybeIt(features.detail.versionNotFound)(\n 'should render Not Found for a version that does not exist',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/9.9.9`);\n\n // The server answers 404 for unknown versions instead of\n // silently serving `latest` under the requested title.\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.response?.statusCode).to.eq(404);\n });\n cy.getByTestId(home.notFound, { timeout: 10000 }).should('be.visible');\n }\n );\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,YAAY,QAAwB;CAClD,MAAM,EAAE,aAAa;CACrB,MAAM,EAAE,SAAS,KAAK,SAAS,OAAO;CAEtC,SAAS,wBAAwB;EAC/B,SAAS,kCAAkC;GACzC,MAAM,UAAU;GAChB,IAAI,aAA4B;GAEhC,IAAI,UAAU;GAEd,iBAAiB;IACf,GAAG,KAAK,kBAAkB;KACxB;KACA,SAAS;KACT,QAAQ;IACV,CAAC,CAAC,CAAC,MAAM,WAAW;KAClB,aAAa,QAAQ,cAAc;KACnC,UAAU,QAAQ,WAAW;IAC/B,CAAC;GACH,CAAC;GAED,gBAAgB;IACd,GAAG,KAAK,oBAAoB;KAC1B;KACA,YAAY,cAAc,KAAA;IAC5B,CAAC;IACD,IAAI,YACF,GAAG,KAAK,oBAAoB,UAAU;IAExC,aAAa;GACf,CAAC;GAED,iBAAA,QAAQ,SAAS,OAAO,gBAAgB,CAAC,CACvC,+EACM;IAGJ,GAAG,UAAU,OAAO,6BAA6B,QAAQ,EAAE,CAAC,CAAC,GAAG,SAAS;IACzE,GAAG,UAAU,OAAO,oCAAoC,QAAQ,EAAE,CAAC,CAAC,GAAG,QAAQ;IAE/E,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,SAAS;IAExD,GAAG,KAAK,YAAY,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,MAAM,iBAAsB;KAClE,OAAO,aAAa,QAAQ,GAAG,CAAC,CAAC,GAAG,QAAQ,YAAY,SAAS;KACjE,OAAO,aAAa,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAI,QAAQ,KAAK;IACvD,CAAC;IACD,GAAG,KAAK,WAAW,EAAE,SAAS,IAAM,CAAC;IACrC,GAAG,YAAY,IAAI,OAAO,CAAC,CAAC,OAAO,YAAY;GACjD,CACF;GAEA,iBAAA,QAAQ,SAAS,OAAO,aAAa,CAAC,CACpC,iFACM;IACJ,GAAG,UAAU,OAAO,6BAA6B,QAAQ,EAAE,CAAC,CAAC,GAAG,SAAS;IAEzE,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,QAAQ,KAAK,SAAS;IAErE,GAAG,KAAK,YAAY,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,MAAM,iBAAsB;KAClE,OAAO,aAAa,QAAQ,GAAG,CAAC,CAAC,GAAG,QAAQ,KAAK,SAAS;IAC5D,CAAC;IACD,GAAG,YAAY,IAAI,OAAO,CAAC,CAAC,OAAO,YAAY;IAC/C,GAAG,YAAY,IAAI,OAAO,CAAC,CAAC,SAAS,OAAO;GAC9C,CACF;GAEA,iBAAA,QAAQ,SAAS,OAAO,eAAe,CAAC,CACtC,mEACM;IACJ,GAAG,UAAU,OAAO,6BAA6B,QAAQ,EAAE,CAAC,CAAC,GAAG,SAAS;IAEzE,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,QAAQ,SAAS;IAIhE,GAAG,KAAK,YAAY,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,MAAM,iBAAsB;KAClE,OAAO,aAAa,UAAU,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG;IACrD,CAAC;IACD,GAAG,YAAY,KAAK,UAAU,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;GACvE,CACF;EACF,CAAC;CACH,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"detail.cjs","names":[],"sources":["../../../src/tests/detail.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Package detail page tests: version-pinned URLs and the wire format of\n * the UI's data requests.\n *\n * The wire format is an observable contract, not an implementation\n * detail: proxies, intercepts and every npm client expect scoped names\n * as literal `/@scope/name`. A ui-components refactor that\n * percent-encoded the `@` broke exactly these suites\n * (verdaccio/verdaccio#6210), which is why it is pinned here on purpose.\n */\nexport function detailTests(config: RegistryConfig) {\n const { features } = config;\n const { package: pkg, home } = config.testIds;\n\n describe('package detail', () => {\n describe('with a published package', () => {\n const pkgName = '@verdaccio/detail-fixture';\n let tempFolder: string | null = null;\n // `unique: true` uniquifies the VERSION (1.0.0-t<ts>), not the name\n let version = '1.0.0';\n\n beforeEach(() => {\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n version = result?.version ?? '1.0.0';\n });\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.detail.scopedWireFormat)(\n 'should request sidebar and readme with the scope literal (@, not %40)',\n () => {\n // Literal-path intercepts: if the UI ever encodes `@` or `/`,\n // these never match and the waits below time out.\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}*`).as('readme');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(`/sidebar/${pkgName}`);\n expect(interception.request.url).to.not.contain('%40');\n });\n cy.wait('@readme', { timeout: 10000 });\n cy.getByTestId(pkg.sidebar).should('be.visible');\n }\n );\n\n maybeIt(features.detail.versionPinned)(\n 'should pin the requested version via the ?v= query on /v/<version> urls',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/${version}`);\n\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(`v=${version}`);\n });\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.sidebar).contains(version);\n }\n );\n\n maybeIt(features.detail.staleVersionsNavigation)(\n 'the versions tab must show the CURRENT package after SPA navigation',\n () => {\n // second fixture with a different version count; the detail\n // routes reuse one mounted component, so navigating A -> B\n // must not leak A's cached versions into B's tab\n const otherPkg = '@verdaccio/stale-nav-fixture';\n let otherTemp: string | null = null;\n cy.task('publishPackage', { pkgName: otherPkg, version: '2.0.0', unique: true }).then(\n (result) => {\n otherTemp = result?.tempFolder ?? null;\n }\n );\n // give the first fixture a second version so the counts differ\n cy.task('publishPackage', { pkgName, version: '1.1.0', unique: true }).then((r) => {\n if (r?.tempFolder) cy.task('cleanupPublished', r.tempFolder);\n });\n\n cy.visit(config.registryUrl);\n cy.contains(`[data-testid=\"${pkg.title}\"]`, pkgName).click();\n cy.getByTestId(pkg.versionsTab, { timeout: 10000 }).click();\n cy.get('[data-testid=\"version-list-text\"]').should('have.length', 2);\n\n // SPA navigation back home via the logo (no full reload);\n // the logo can render more than once (desktop + mobile nav)\n cy.getByTestId(config.testIds.header.defaultLogo).first().click();\n cy.contains(`[data-testid=\"${pkg.title}\"]`, otherPkg).click();\n cy.getByTestId(pkg.versionsTab, { timeout: 10000 }).click();\n // stale-state bug: B showed A's list (length 2) here\n cy.get('[data-testid=\"version-list-text\"]').should('have.length', 1);\n\n cy.task('unpublishPackage', { pkgName: otherPkg, tempFolder: otherTemp ?? undefined });\n cy.then(() => {\n if (otherTemp) cy.task('cleanupPublished', otherTemp);\n });\n }\n );\n\n maybeIt(features.detail.versionNotFound)(\n 'should render Not Found for a version that does not exist',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`).as('sidebar');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/9.9.9`);\n\n // The server answers 404 for unknown versions instead of\n // silently serving `latest` under the requested title.\n cy.wait('@sidebar', { timeout: 10000 }).then((interception: any) => {\n expect(interception.response?.statusCode).to.eq(404);\n });\n cy.getByTestId(home.notFound, { timeout: 10000 }).should('be.visible');\n }\n );\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -13,6 +13,17 @@ function failureModeTests(config) {
|
|
|
13
13
|
const { features } = config;
|
|
14
14
|
const { home, header, errors, package: pkg } = config.testIds;
|
|
15
15
|
describe("failure modes", () => {
|
|
16
|
+
require_features.maybeIt(features.failureModes.searchError)("a search 5xx must show an error, not \"no results found\"", () => {
|
|
17
|
+
cy.intercept("GET", "**/-/verdaccio/data/search/**", {
|
|
18
|
+
statusCode: 500,
|
|
19
|
+
body: { error: "internal server error" }
|
|
20
|
+
}).as("search");
|
|
21
|
+
cy.visit(config.registryUrl);
|
|
22
|
+
cy.getByTestId(header.searchContainer).find("input").type("anything", { delay: 20 });
|
|
23
|
+
cy.wait("@search", { timeout: 1e4 });
|
|
24
|
+
cy.contains("Something went wrong while searching", { timeout: 1e4 }).should("be.visible");
|
|
25
|
+
cy.contains("No results found").should("not.exist");
|
|
26
|
+
});
|
|
16
27
|
require_features.maybeIt(features.failureModes.homeServerError)("a 5xx on the package list must not render the empty-registry onboarding", () => {
|
|
17
28
|
cy.intercept("GET", "/-/verdaccio/data/packages", {
|
|
18
29
|
statusCode: 500,
|
|
@@ -51,6 +62,17 @@ function failureModeTests(config) {
|
|
|
51
62
|
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
52
63
|
tempFolder = null;
|
|
53
64
|
});
|
|
65
|
+
require_features.maybeIt(features.failureModes.downloadError)("a failed tarball download must show an error snackbar, not silence", () => {
|
|
66
|
+
cy.intercept("GET", "**/*.tgz*", {
|
|
67
|
+
statusCode: 500,
|
|
68
|
+
body: "boom"
|
|
69
|
+
}).as("tarball");
|
|
70
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
71
|
+
cy.getByTestId(pkg.sidebar, { timeout: 1e4 }).should("be.visible");
|
|
72
|
+
cy.getByTestId(pkg.downloadTarballBtn).click();
|
|
73
|
+
cy.wait("@tarball", { timeout: 1e4 });
|
|
74
|
+
cy.contains("The tarball could not be downloaded", { timeout: 1e4 }).should("be.visible");
|
|
75
|
+
});
|
|
54
76
|
require_features.maybeIt(features.failureModes.detailErrorState)("a 5xx on the detail data must render an error state, not a blank page", () => {
|
|
55
77
|
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`, {
|
|
56
78
|
statusCode: 500,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure-modes.cjs","names":[],"sources":["../../../src/tests/failure-modes.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Failure-mode tests: what the UI shows when the backend misbehaves.\n *\n * A registry that is down must never look like a healthy empty registry\n * (that invites the user to publish into the void), and a failed detail\n * request must render an error state instead of a blank page. These are\n * the failure classes fixed by verdaccio/verdaccio#6210 — the strict\n * assertions are feature-gated probes until that ships.\n */\nexport function failureModeTests(config: RegistryConfig) {\n const { features } = config;\n const { home, header, errors, package: pkg } = config.testIds;\n\n describe('failure modes', () => {\n maybeIt(features.failureModes.homeServerError)(\n 'a 5xx on the package list must not render the empty-registry onboarding',\n () => {\n cy.intercept('GET', '/-/verdaccio/data/packages', {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.wait('@pkgs', { timeout: 10000 });\n\n // The header must survive and the onboarding card must not\n // appear — a dead backend is not an empty registry.\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(home.helpCard).should('not.exist');\n }\n );\n\n maybeIt(features.failureModes.homeNetworkError)(\n 'an unreachable backend must not render the empty-registry onboarding',\n () => {\n cy.intercept('GET', '/-/verdaccio/data/packages', { forceNetworkError: true }).as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.wait('@pkgs', { timeout: 10000 });\n\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(home.helpCard).should('not.exist');\n cy.getByTestId(errors.genericError, { timeout: 10000 }).should('be.visible');\n }\n );\n\n describe('with a published package', () => {\n const pkgName = '@verdaccio/failure-fixture';\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.failureModes.detailErrorState)(\n 'a 5xx on the detail data must render an error state, not a blank page',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`, {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}*`, {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('readme');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.wait('@sidebar', { timeout: 10000 });\n\n // Error page instead of a blank layout whose tabs crash the\n // whole app; the header must survive.\n cy.getByTestId(errors.genericError, { timeout: 10000 }).should('be.visible');\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(pkg.sidebar).should('not.exist');\n }\n );\n });\n });\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,iBAAiB,QAAwB;CACvD,MAAM,EAAE,aAAa;CACrB,MAAM,EAAE,MAAM,QAAQ,QAAQ,SAAS,QAAQ,OAAO;CAEtD,SAAS,uBAAuB;EAC9B,iBAAA,QAAQ,SAAS,aAAa,eAAe,CAAC,CAC5C,iFACM;GACJ,GAAG,UAAU,OAAO,8BAA8B;IAChD,YAAY;IACZ,MAAM,EAAE,OAAO,wBAAwB;GACzC,CAAC,CAAC,CAAC,GAAG,MAAM;GAEZ,GAAG,MAAM,OAAO,WAAW;GAC3B,GAAG,KAAK,SAAS,EAAE,SAAS,IAAM,CAAC;GAInC,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY;GACpD,GAAG,YAAY,KAAK,QAAQ,CAAC,CAAC,OAAO,WAAW;EAClD,CACF;EAEA,iBAAA,QAAQ,SAAS,aAAa,gBAAgB,CAAC,CAC7C,8EACM;GACJ,GAAG,UAAU,OAAO,8BAA8B,EAAE,mBAAmB,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM;GAExF,GAAG,MAAM,OAAO,WAAW;GAC3B,GAAG,KAAK,SAAS,EAAE,SAAS,IAAM,CAAC;GAEnC,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY;GACpD,GAAG,YAAY,KAAK,QAAQ,CAAC,CAAC,OAAO,WAAW;GAChD,GAAG,YAAY,OAAO,cAAc,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;EAC7E,CACF;EAEA,SAAS,kCAAkC;GACzC,MAAM,UAAU;GAChB,IAAI,aAA4B;GAEhC,iBAAiB;IACf,GAAG,KAAK,kBAAkB;KACxB;KACA,SAAS;KACT,QAAQ;IACV,CAAC,CAAC,CAAC,MAAM,WAAW;KAClB,aAAa,QAAQ,cAAc;IACrC,CAAC;GACH,CAAC;GAED,gBAAgB;IACd,GAAG,KAAK,oBAAoB;KAC1B;KACA,YAAY,cAAc,KAAA;IAC5B,CAAC;IACD,IAAI,YACF,GAAG,KAAK,oBAAoB,UAAU;IAExC,aAAa;GACf,CAAC;GAED,iBAAA,QAAQ,SAAS,aAAa,gBAAgB,CAAC,CAC7C,+EACM;IACJ,GAAG,UAAU,OAAO,6BAA6B,QAAQ,IAAI;KAC3D,YAAY;KACZ,MAAM,EAAE,OAAO,wBAAwB;IACzC,CAAC,CAAC,CAAC,GAAG,SAAS;IACf,GAAG,UAAU,OAAO,oCAAoC,QAAQ,IAAI;KAClE,YAAY;KACZ,MAAM,EAAE,OAAO,wBAAwB;IACzC,CAAC,CAAC,CAAC,GAAG,QAAQ;IAEd,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,SAAS;IACxD,GAAG,KAAK,YAAY,EAAE,SAAS,IAAM,CAAC;IAItC,GAAG,YAAY,OAAO,cAAc,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;IAC3E,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY;IACpD,GAAG,YAAY,IAAI,OAAO,CAAC,CAAC,OAAO,WAAW;GAChD,CACF;EACF,CAAC;CACH,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"failure-modes.cjs","names":[],"sources":["../../../src/tests/failure-modes.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Failure-mode tests: what the UI shows when the backend misbehaves.\n *\n * A registry that is down must never look like a healthy empty registry\n * (that invites the user to publish into the void), and a failed detail\n * request must render an error state instead of a blank page. These are\n * the failure classes fixed by verdaccio/verdaccio#6210 — the strict\n * assertions are feature-gated probes until that ships.\n */\nexport function failureModeTests(config: RegistryConfig) {\n const { features } = config;\n const { home, header, errors, package: pkg } = config.testIds;\n\n describe('failure modes', () => {\n maybeIt(features.failureModes.searchError)(\n 'a search 5xx must show an error, not \"no results found\"',\n () => {\n cy.intercept('GET', '**/-/verdaccio/data/search/**', {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('search');\n\n cy.visit(config.registryUrl);\n cy.getByTestId(header.searchContainer).find('input').type('anything', { delay: 20 });\n cy.wait('@search', { timeout: 10000 });\n\n // en-US bundle: autoComplete.error\n cy.contains('Something went wrong while searching', { timeout: 10000 }).should(\n 'be.visible'\n );\n cy.contains('No results found').should('not.exist');\n }\n );\n\n maybeIt(features.failureModes.homeServerError)(\n 'a 5xx on the package list must not render the empty-registry onboarding',\n () => {\n cy.intercept('GET', '/-/verdaccio/data/packages', {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.wait('@pkgs', { timeout: 10000 });\n\n // The header must survive and the onboarding card must not\n // appear — a dead backend is not an empty registry.\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(home.helpCard).should('not.exist');\n }\n );\n\n maybeIt(features.failureModes.homeNetworkError)(\n 'an unreachable backend must not render the empty-registry onboarding',\n () => {\n cy.intercept('GET', '/-/verdaccio/data/packages', { forceNetworkError: true }).as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.wait('@pkgs', { timeout: 10000 });\n\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(home.helpCard).should('not.exist');\n cy.getByTestId(errors.genericError, { timeout: 10000 }).should('be.visible');\n }\n );\n\n describe('with a published package', () => {\n const pkgName = '@verdaccio/failure-fixture';\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.failureModes.downloadError)(\n 'a failed tarball download must show an error snackbar, not silence',\n () => {\n cy.intercept('GET', '**/*.tgz*', {\n statusCode: 500,\n body: 'boom',\n }).as('tarball');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.getByTestId(pkg.sidebar, { timeout: 10000 }).should('be.visible');\n cy.getByTestId(pkg.downloadTarballBtn).click();\n cy.wait('@tarball', { timeout: 10000 });\n\n // en-US bundle: error.download-tarball\n cy.contains('The tarball could not be downloaded', { timeout: 10000 }).should(\n 'be.visible'\n );\n }\n );\n\n maybeIt(features.failureModes.detailErrorState)(\n 'a 5xx on the detail data must render an error state, not a blank page',\n () => {\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}*`, {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}*`, {\n statusCode: 500,\n body: { error: 'internal server error' },\n }).as('readme');\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.wait('@sidebar', { timeout: 10000 });\n\n // Error page instead of a blank layout whose tabs crash the\n // whole app; the header must survive.\n cy.getByTestId(errors.genericError, { timeout: 10000 }).should('be.visible');\n cy.getByTestId(header.container).should('be.visible');\n cy.getByTestId(pkg.sidebar).should('not.exist');\n }\n );\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -9,14 +9,18 @@ const require_detail = require("./detail.cjs");
|
|
|
9
9
|
const require_failure_modes = require("./failure-modes.cjs");
|
|
10
10
|
const require_i18n_keys = require("./i18n-keys.cjs");
|
|
11
11
|
const require_signup = require("./signup.cjs");
|
|
12
|
+
const require_manifest_rendering = require("./manifest-rendering.cjs");
|
|
13
|
+
const require_session = require("./session.cjs");
|
|
12
14
|
exports.changePasswordTests = require_change_password.changePasswordTests;
|
|
13
15
|
exports.detailTests = require_detail.detailTests;
|
|
14
16
|
exports.failureModeTests = require_failure_modes.failureModeTests;
|
|
15
17
|
exports.homeTests = require_home.homeTests;
|
|
16
18
|
exports.i18nKeyTests = require_i18n_keys.i18nKeyTests;
|
|
17
19
|
exports.layoutTests = require_layout.layoutTests;
|
|
20
|
+
exports.manifestRenderingTests = require_manifest_rendering.manifestRenderingTests;
|
|
18
21
|
exports.publishTests = require_publish.publishTests;
|
|
19
22
|
exports.searchTests = require_search.searchTests;
|
|
23
|
+
exports.sessionTests = require_session.sessionTests;
|
|
20
24
|
exports.settingsTests = require_settings.settingsTests;
|
|
21
25
|
exports.signinTests = require_signin.signinTests;
|
|
22
26
|
exports.signupTests = require_signup.signupTests;
|