@verdaccio/e2e-ui 2.6.0 → 2.8.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 +40 -3
- package/build/cjs/features.cjs.map +1 -1
- package/build/cjs/index.cjs +10 -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/testIds.cjs +23 -1
- package/build/cjs/testIds.cjs.map +1 -1
- package/build/cjs/tests/change-password.cjs +20 -7
- 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/home-detail-ux.cjs +166 -0
- package/build/cjs/tests/home-detail-ux.cjs.map +1 -0
- package/build/cjs/tests/index.cjs +6 -0
- package/build/cjs/tests/manifest-rendering.cjs +73 -0
- package/build/cjs/tests/manifest-rendering.cjs.map +1 -0
- package/build/cjs/tests/publish.cjs +1 -1
- package/build/cjs/tests/publish.cjs.map +1 -1
- package/build/cjs/tests/session.cjs +49 -0
- package/build/cjs/tests/session.cjs.map +1 -0
- package/build/cjs/tests/settings.cjs +33 -2
- package/build/cjs/tests/settings.cjs.map +1 -1
- package/build/esm/features.js +40 -3
- package/build/esm/features.js.map +1 -1
- package/build/esm/index.js +8 -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/testIds.js +23 -1
- package/build/esm/testIds.js.map +1 -1
- package/build/esm/tests/change-password.js +20 -7
- 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/home-detail-ux.js +166 -0
- package/build/esm/tests/home-detail-ux.js.map +1 -0
- package/build/esm/tests/index.js +4 -1
- package/build/esm/tests/manifest-rendering.js +73 -0
- package/build/esm/tests/manifest-rendering.js.map +1 -0
- package/build/esm/tests/publish.js +1 -1
- package/build/esm/tests/publish.js.map +1 -1
- package/build/esm/tests/session.js +49 -0
- package/build/esm/tests/session.js.map +1 -0
- package/build/esm/tests/settings.js +33 -2
- package/build/esm/tests/settings.js.map +1 -1
- package/build/features.d.ts +105 -0
- package/build/index.d.ts +1 -1
- package/build/tasks/publish.d.ts +9 -0
- package/build/testIds.d.ts +34 -0
- package/build/tests/home-detail-ux.d.ts +10 -0
- package/build/tests/index.d.ts +3 -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/esm/tests/index.js
CHANGED
|
@@ -9,4 +9,7 @@ import { detailTests } from "./detail.js";
|
|
|
9
9
|
import { failureModeTests } from "./failure-modes.js";
|
|
10
10
|
import { i18nKeyTests } from "./i18n-keys.js";
|
|
11
11
|
import { signupTests } from "./signup.js";
|
|
12
|
-
|
|
12
|
+
import { manifestRenderingTests } from "./manifest-rendering.js";
|
|
13
|
+
import { sessionTests } from "./session.js";
|
|
14
|
+
import { homeDetailUxTests } from "./home-detail-ux.js";
|
|
15
|
+
export { changePasswordTests, detailTests, failureModeTests, homeDetailUxTests, homeTests, i18nKeyTests, layoutTests, manifestRenderingTests, publishTests, searchTests, sessionTests, settingsTests, signinTests, signupTests };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { maybeIt } from "../features.js";
|
|
2
|
+
//#region src/tests/manifest-rendering.ts
|
|
3
|
+
/**
|
|
4
|
+
* Manifest-shape rendering tests.
|
|
5
|
+
*
|
|
6
|
+
* npm does not validate these fields on publish: `repository`, `funding`
|
|
7
|
+
* and `bugs` are all valid in string form, `funding` also as an array,
|
|
8
|
+
* and contributors commonly ship without an email. Real packuments carry
|
|
9
|
+
* all of these — the UI must render them instead of silently dropping
|
|
10
|
+
* whole sections (the bug family fixed in verdaccio/verdaccio#6210,
|
|
11
|
+
* where every probe here failed).
|
|
12
|
+
*
|
|
13
|
+
* Requires the `manifest` override support in the publishPackage task.
|
|
14
|
+
*/
|
|
15
|
+
function manifestRenderingTests(config) {
|
|
16
|
+
const { features } = config;
|
|
17
|
+
const { package: pkg } = config.testIds;
|
|
18
|
+
describe("manifest rendering", () => {
|
|
19
|
+
const pkgName = "@verdaccio/manifest-fixture";
|
|
20
|
+
let tempFolder = null;
|
|
21
|
+
const publishWith = (manifest) => cy.task("publishPackage", {
|
|
22
|
+
pkgName,
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
unique: true,
|
|
25
|
+
manifest
|
|
26
|
+
}).then((result) => {
|
|
27
|
+
tempFolder = result?.tempFolder ?? null;
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cy.task("unpublishPackage", {
|
|
31
|
+
pkgName,
|
|
32
|
+
tempFolder: tempFolder ?? void 0
|
|
33
|
+
});
|
|
34
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
35
|
+
tempFolder = null;
|
|
36
|
+
});
|
|
37
|
+
maybeIt(features.manifestRendering.stringForms)("string repository, funding array and string bugs must all render", () => {
|
|
38
|
+
publishWith({
|
|
39
|
+
repository: "git://github.com/verdaccio/verdaccio.git",
|
|
40
|
+
funding: [{
|
|
41
|
+
type: "opencollective",
|
|
42
|
+
url: "https://opencollective.com/verdaccio"
|
|
43
|
+
}],
|
|
44
|
+
bugs: "https://github.com/verdaccio/verdaccio/issues"
|
|
45
|
+
});
|
|
46
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
47
|
+
cy.getByTestId(pkg.sidebar, { timeout: 1e4 }).should("be.visible");
|
|
48
|
+
cy.getByTestId(pkg.sidebar).find("a[href^=\"https://github.com/verdaccio/verdaccio\"]").should("be.visible");
|
|
49
|
+
cy.getByTestId(pkg.sidebar).find("a[href=\"https://opencollective.com/verdaccio\"]").should("exist");
|
|
50
|
+
cy.get("a[href=\"https://github.com/verdaccio/verdaccio/issues\"]").should("exist");
|
|
51
|
+
});
|
|
52
|
+
maybeIt(features.manifestRendering.gravatarAvatars)("maintainer emails must render gravatar avatars on the sidebar", () => {
|
|
53
|
+
publishWith({ contributors: [{
|
|
54
|
+
name: "Ava Tester",
|
|
55
|
+
email: "ava@example.com"
|
|
56
|
+
}] });
|
|
57
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
58
|
+
cy.getByTestId(pkg.sidebar, { timeout: 1e4 }).should("be.visible");
|
|
59
|
+
cy.getByTestId(pkg.sidebar).find("img[src*=\"gravatar.com/avatar\"]").should("have.length.at.least", 1);
|
|
60
|
+
});
|
|
61
|
+
maybeIt(features.manifestRendering.developersWithoutEmail)("contributors without email must not collapse into one", () => {
|
|
62
|
+
publishWith({ contributors: [{ name: "alice-noemail" }, { name: "bob-noemail" }] });
|
|
63
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
64
|
+
cy.getByTestId(pkg.sidebar, { timeout: 1e4 }).should("be.visible");
|
|
65
|
+
cy.getByTestId("alice-noemail").should("exist");
|
|
66
|
+
cy.getByTestId("bob-noemail").should("exist");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { manifestRenderingTests };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=manifest-rendering.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-rendering.js","names":[],"sources":["../../../src/tests/manifest-rendering.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Manifest-shape rendering tests.\n *\n * npm does not validate these fields on publish: `repository`, `funding`\n * and `bugs` are all valid in string form, `funding` also as an array,\n * and contributors commonly ship without an email. Real packuments carry\n * all of these — the UI must render them instead of silently dropping\n * whole sections (the bug family fixed in verdaccio/verdaccio#6210,\n * where every probe here failed).\n *\n * Requires the `manifest` override support in the publishPackage task.\n */\nexport function manifestRenderingTests(config: RegistryConfig) {\n const { features } = config;\n const { package: pkg } = config.testIds;\n\n describe('manifest rendering', () => {\n const pkgName = '@verdaccio/manifest-fixture';\n let tempFolder: string | null = null;\n\n const publishWith = (manifest: Record<string, unknown>) =>\n cy\n .task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n manifest,\n })\n .then((result) => {\n tempFolder = result?.tempFolder ?? null;\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.manifestRendering.stringForms)(\n 'string repository, funding array and string bugs must all render',\n () => {\n // NOTE: the npm CLI normalizes string `repository`/`bugs` into their\n // object forms on publish, so what reaches the registry here is the\n // normalized shape with the git:// protocol kept. The RAW string\n // forms only arrive via uplink packuments and stay pinned by the\n // ui-components unit tests; this spec pins what survives publish:\n // the git:// -> https rewrite, the funding array and the bugs url.\n publishWith({\n repository: 'git://github.com/verdaccio/verdaccio.git',\n funding: [{ type: 'opencollective', url: 'https://opencollective.com/verdaccio' }],\n bugs: 'https://github.com/verdaccio/verdaccio/issues',\n });\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.getByTestId(pkg.sidebar, { timeout: 10000 }).should('be.visible');\n\n // repository: rendered, rewritten to a browsable https url\n // (prefix match: normalization may or may not keep the .git suffix)\n cy.getByTestId(pkg.sidebar)\n .find('a[href^=\"https://github.com/verdaccio/verdaccio\"]')\n .should('be.visible');\n // funding (array form): the fund button links to the first entry\n cy.getByTestId(pkg.sidebar)\n .find('a[href=\"https://opencollective.com/verdaccio\"]')\n .should('exist');\n // bugs (string form): open-an-issue action links to it\n cy.get('a[href=\"https://github.com/verdaccio/verdaccio/issues\"]').should('exist');\n }\n );\n\n maybeIt(features.manifestRendering.gravatarAvatars)(\n 'maintainer emails must render gravatar avatars on the sidebar',\n () => {\n publishWith({\n contributors: [{ name: 'Ava Tester', email: 'ava@example.com' }],\n });\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.getByTestId(pkg.sidebar, { timeout: 10000 }).should('be.visible');\n\n // the sidebar endpoint computes the gravatar url server-side;\n // the avatar <img> must actually carry it\n cy.getByTestId(pkg.sidebar)\n .find('img[src*=\"gravatar.com/avatar\"]')\n .should('have.length.at.least', 1);\n }\n );\n\n maybeIt(features.manifestRendering.developersWithoutEmail)(\n 'contributors without email must not collapse into one',\n () => {\n publishWith({\n contributors: [{ name: 'alice-noemail' }, { name: 'bob-noemail' }],\n });\n\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.getByTestId(pkg.sidebar, { timeout: 10000 }).should('be.visible');\n\n // each Person renders data-testid=<name>; both must survive dedupe\n cy.getByTestId('alice-noemail').should('exist');\n cy.getByTestId('bob-noemail').should('exist');\n }\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,SAAgB,uBAAuB,QAAwB;CAC7D,MAAM,EAAE,aAAa;CACrB,MAAM,EAAE,SAAS,QAAQ,OAAO;CAEhC,SAAS,4BAA4B;EACnC,MAAM,UAAU;EAChB,IAAI,aAA4B;EAEhC,MAAM,eAAe,aACnB,GACG,KAAK,kBAAkB;GACtB;GACA,SAAS;GACT,QAAQ;GACR;EACF,CAAC,CAAC,CACD,MAAM,WAAW;GAChB,aAAa,QAAQ,cAAc;EACrC,CAAC;EAEL,gBAAgB;GACd,GAAG,KAAK,oBAAoB;IAC1B;IACA,YAAY,cAAc,KAAA;GAC5B,CAAC;GACD,IAAI,YACF,GAAG,KAAK,oBAAoB,UAAU;GAExC,aAAa;EACf,CAAC;EAED,QAAQ,SAAS,kBAAkB,WAAW,CAAC,CAC7C,0EACM;GAOJ,YAAY;IACV,YAAY;IACZ,SAAS,CAAC;KAAE,MAAM;KAAkB,KAAK;IAAuC,CAAC;IACjF,MAAM;GACR,CAAC;GAED,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,SAAS;GACxD,GAAG,YAAY,IAAI,SAAS,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;GAInE,GAAG,YAAY,IAAI,OAAO,CAAC,CACxB,KAAK,qDAAmD,CAAC,CACzD,OAAO,YAAY;GAEtB,GAAG,YAAY,IAAI,OAAO,CAAC,CACxB,KAAK,kDAAgD,CAAC,CACtD,OAAO,OAAO;GAEjB,GAAG,IAAI,2DAAyD,CAAC,CAAC,OAAO,OAAO;EAClF,CACF;EAEA,QAAQ,SAAS,kBAAkB,eAAe,CAAC,CACjD,uEACM;GACJ,YAAY,EACV,cAAc,CAAC;IAAE,MAAM;IAAc,OAAO;GAAkB,CAAC,EACjE,CAAC;GAED,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,SAAS;GACxD,GAAG,YAAY,IAAI,SAAS,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;GAInE,GAAG,YAAY,IAAI,OAAO,CAAC,CACxB,KAAK,mCAAiC,CAAC,CACvC,OAAO,wBAAwB,CAAC;EACrC,CACF;EAEA,QAAQ,SAAS,kBAAkB,sBAAsB,CAAC,CACxD,+DACM;GACJ,YAAY,EACV,cAAc,CAAC,EAAE,MAAM,gBAAgB,GAAG,EAAE,MAAM,cAAc,CAAC,EACnE,CAAC;GAED,GAAG,MAAM,GAAG,OAAO,YAAY,gBAAgB,SAAS;GACxD,GAAG,YAAY,IAAI,SAAS,EAAE,SAAS,IAAM,CAAC,CAAC,CAAC,OAAO,YAAY;GAGnE,GAAG,YAAY,eAAe,CAAC,CAAC,OAAO,OAAO;GAC9C,GAAG,YAAY,aAAa,CAAC,CAAC,OAAO,OAAO;EAC9C,CACF;CACF,CAAC;AACH"}
|
|
@@ -91,7 +91,7 @@ function publishTests(config) {
|
|
|
91
91
|
cy.wait("@pkgs");
|
|
92
92
|
cy.getByTestId(pkg.title).first().click();
|
|
93
93
|
cy.wait("@sidebar");
|
|
94
|
-
cy.getByTestId(pkg.keywordList).should("be.visible");
|
|
94
|
+
cy.getByTestId(pkg.keywordList).scrollIntoView().should("be.visible");
|
|
95
95
|
cy.getByTestId(pkg.keywordList).should("contain.text", "verdaccio");
|
|
96
96
|
cy.getByTestId(pkg.keywordList).should("contain.text", "e2e");
|
|
97
97
|
cy.getByTestId(pkg.keywordList).should("contain.text", "test");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.js","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\nexport function publishTests(config: RegistryConfig) {\n const { header, package: pkg } = config.testIds;\n const { markdownBody, loginDialog } = config.selectors;\n const { features } = config;\n /**\n * Log in once, reuse the session across tests in the requested suite.\n * `cy.session` caches cookies + localStorage keyed on the first\n * argument, so subsequent calls restore without hitting the network.\n */\n const loginOnce = (sessionName: string) => {\n cy.session(\n [sessionName, config.credentials.user],\n () => {\n cy.visit(config.registryUrl);\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n },\n {\n validate() {\n cy.request({\n url: `${config.registryUrl}/-/verdaccio/data/packages`,\n failOnStatusCode: false,\n })\n .its('status')\n .should('be.oneOf', [200, 304]);\n },\n cacheAcrossSpecs: true,\n }\n );\n };\n\n describe('publish', () => {\n const pkgName = '@verdaccio/pkg-scoped';\n // Single source of truth for the dependency the publish fixture\n // writes into its package.json. The dependencies-tab assertion\n // reads these back to verify the UI rendered both fields.\n const depName = 'debug';\n const depVersion = '4.0.0';\n // Per-test state so afterEach can clean up the specific publish\n // that this test created (temp folder + registry entry).\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}`).as('readme');\n\n // Publish a fresh copy for this test. `unique: true` appends a\n // timestamp suffix so the version is distinct per test even when\n // the registry briefly has a stale copy from a prior afterEach.\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n dependencies: { [depName]: depVersion },\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce('publish-suite');\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n // Remove the package from the registry AND the local temp folder.\n // Run both regardless of test outcome so the next test starts\n // clean. `unpublishPackage` treats 404 as success, so a re-run\n // after a half-published state is still safe.\n cy.task('unpublishPackage', { pkgName, tempFolder: tempFolder ?? undefined });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n it('should have one published package', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).should('have.length.at.least', 1);\n });\n\n it('should navigate to page detail', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n });\n\n it('should have readme content', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.readme).should('be.visible');\n cy.get(markdownBody).should('have.length', 1);\n // publishPackage writes a README whose body contains \"e2e testing\".\n cy.contains(markdownBody, /test/);\n cy.contains(`${markdownBody} h1`, pkgName).should('be.visible');\n });\n\n it('should render the sidebar with install commands for npm, yarn, pnpm', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.installList).within(() => {\n cy.getByTestId(pkg.installNpm).should('be.visible');\n cy.getByTestId(pkg.installYarn).should('be.visible');\n cy.getByTestId(pkg.installPnpm).should('be.visible');\n });\n cy.getByTestId(pkg.installNpm).should('contain.text', pkgName);\n });\n\n it('should render the sidebar keywords from the published manifest', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n // publishPackage writes `keywords: ['verdaccio', 'e2e', 'test']`\n // into the generated package.json.\n cy.getByTestId(pkg.keywordList).should('be.visible');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'verdaccio');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'e2e');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'test');\n });\n\n it('should click on dependencies tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.dependenciesTab).click();\n cy.wait(100);\n cy.getByTestId(pkg.dependencies).should('have.length', 1);\n\n // The dep Chip uses the dep name as its data-testid (dynamic\n // Verdaccio convention, see DependencyBlock.tsx:68), and its\n // label is `\"${name}: ${version}\"` via the `dependencies.\n // dependency-block` i18n key. Assert BOTH fields are rendered.\n cy.getByTestId(depName)\n .should('be.visible')\n .and('contain.text', depName)\n .and('contain.text', depVersion);\n\n // Also verify the Chip text matches the exact \"name: version\"\n // format so a regression in the label template would fail here\n // rather than pass via a loose substring match.\n cy.getByTestId(depName)\n .invoke('text')\n .should('match', new RegExp(`${depName}\\\\s*:\\\\s*${depVersion}`));\n });\n\n it('should click on versions tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.versionsTab).click();\n // With `unique: true` the version becomes `1.0.0-t<timestamp>`,\n // but \"1.0.0\" still appears as a substring — match loosely.\n cy.getByTestId(pkg.tagLatest)\n .children()\n .invoke('text')\n .should('match', /1\\.0\\.0/);\n });\n\n it('should click on uplinks tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.uplinksTab).click();\n cy.getByTestId(pkg.noUplinks).should('be.visible');\n });\n\n // ── Action-bar FABs: tarball download + raw viewer ─────────────\n // Both buttons live in the sidebar ActionBar. They're gated on\n // `web.showDownloadTarball` / `web.showRaw` (both default to true\n // in the ui-theme's AppConfigurationProvider) and each test is\n // also guarded by a feature flag so branches that ship a\n // different action bar can skip cleanly.\n\n maybeIt(features.publish.downloadTarball)(\n 'should fetch the tarball when the download button is clicked',\n () => {\n // The download provider hits the package manifest's dist.tarball\n // URL directly. For our published fixture the filename looks\n // like `pkg-scoped-1.0.0-t<ts>.tgz`, served from\n // `/<pkg>/-/<filename>.tgz`. Intercept before clicking.\n cy.intercept('GET', '**/pkg-scoped-*.tgz').as('tarballFetch');\n\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n cy.getByTestId(pkg.downloadTarballBtn).should('be.visible').click();\n\n // The fetch should fire and return 200. We can't assert on the\n // actual file landing on disk — Cypress doesn't track OS-level\n // downloads — but a successful GET proves the end-to-end path\n // from click → download provider → registry.\n cy.wait('@tarballFetch', { timeout: 10000 }).its('response.statusCode').should('eq', 200);\n }\n );\n\n maybeIt(features.publish.rawViewer)(\n 'should open the raw manifest viewer when the raw button is clicked',\n () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n // RawViewer is a full-screen MUI Dialog — initially unmounted\n // because `isOpen=false` keeps Dialog closed and Cypress won't\n // find it. Clicking the FAB flips `isOpen` to true.\n cy.getByTestId(pkg.rawBtn).should('be.visible').click();\n\n cy.getByTestId(pkg.rawViewerDialog, { timeout: 5000 }).should('be.visible');\n // The ReactJson viewer renders the package manifest — the\n // package name should appear somewhere in the serialized JSON.\n cy.getByTestId(pkg.rawViewerDialog).should('contain.text', pkgName);\n\n // Close via the X button and confirm the dialog goes away so\n // subsequent tests don't inherit an open overlay.\n cy.getByTestId(pkg.closeRawViewer).click();\n cy.getByTestId(pkg.rawViewerDialog).should('not.exist');\n }\n );\n });\n\n describe('private package tarball downloads', () => {\n const privatePkgName = '@private/tarball-fixture';\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', '**/-/verdaccio/data/sidebar/@private/tarball-fixture*').as('sidebar');\n\n cy.task('publishPackage', {\n pkgName: privatePkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce('private-tarball-suite');\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName: privatePkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.publish.privateDownloadTarball)(\n 'should fetch a private tarball from the home package list',\n () => {\n cy.intercept('GET', '**/tarball-fixture-*.tgz').as('privateTarballFetch');\n\n cy.wait('@pkgs');\n cy.contains(`[data-testid=\"${pkg.title}\"]`, privatePkgName)\n .should('be.visible')\n .closest(`[data-testid=\"${pkg.itemList}\"]`)\n .find(`[data-testid=\"${pkg.downloadTarball}\"]`)\n .should('be.visible')\n .click();\n\n cy.wait('@privateTarballFetch', { timeout: 10000 })\n .its('response.statusCode')\n .should('eq', 200);\n }\n );\n\n maybeIt(features.publish.privateDownloadTarball)(\n 'should fetch a private tarball from the package sidebar',\n () => {\n cy.intercept('GET', '**/tarball-fixture-*.tgz').as('privateTarballFetch');\n\n cy.wait('@pkgs');\n cy.contains(`[data-testid=\"${pkg.title}\"]`, privatePkgName).click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.downloadTarballBtn).should('be.visible').click();\n\n cy.wait('@privateTarballFetch', { timeout: 10000 })\n .its('response.statusCode')\n .should('eq', 200);\n }\n );\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publish.js","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\nexport function publishTests(config: RegistryConfig) {\n const { header, package: pkg } = config.testIds;\n const { markdownBody, loginDialog } = config.selectors;\n const { features } = config;\n /**\n * Log in once, reuse the session across tests in the requested suite.\n * `cy.session` caches cookies + localStorage keyed on the first\n * argument, so subsequent calls restore without hitting the network.\n */\n const loginOnce = (sessionName: string) => {\n cy.session(\n [sessionName, config.credentials.user],\n () => {\n cy.visit(config.registryUrl);\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n },\n {\n validate() {\n cy.request({\n url: `${config.registryUrl}/-/verdaccio/data/packages`,\n failOnStatusCode: false,\n })\n .its('status')\n .should('be.oneOf', [200, 304]);\n },\n cacheAcrossSpecs: true,\n }\n );\n };\n\n describe('publish', () => {\n const pkgName = '@verdaccio/pkg-scoped';\n // Single source of truth for the dependency the publish fixture\n // writes into its package.json. The dependencies-tab assertion\n // reads these back to verify the UI rendered both fields.\n const depName = 'debug';\n const depVersion = '4.0.0';\n // Per-test state so afterEach can clean up the specific publish\n // that this test created (temp folder + registry entry).\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}`).as('readme');\n\n // Publish a fresh copy for this test. `unique: true` appends a\n // timestamp suffix so the version is distinct per test even when\n // the registry briefly has a stale copy from a prior afterEach.\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n dependencies: { [depName]: depVersion },\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce('publish-suite');\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n // Remove the package from the registry AND the local temp folder.\n // Run both regardless of test outcome so the next test starts\n // clean. `unpublishPackage` treats 404 as success, so a re-run\n // after a half-published state is still safe.\n cy.task('unpublishPackage', { pkgName, tempFolder: tempFolder ?? undefined });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n it('should have one published package', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).should('have.length.at.least', 1);\n });\n\n it('should navigate to page detail', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n });\n\n it('should have readme content', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.readme).should('be.visible');\n cy.get(markdownBody).should('have.length', 1);\n // publishPackage writes a README whose body contains \"e2e testing\".\n cy.contains(markdownBody, /test/);\n cy.contains(`${markdownBody} h1`, pkgName).should('be.visible');\n });\n\n it('should render the sidebar with install commands for npm, yarn, pnpm', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.installList).within(() => {\n cy.getByTestId(pkg.installNpm).should('be.visible');\n cy.getByTestId(pkg.installYarn).should('be.visible');\n cy.getByTestId(pkg.installPnpm).should('be.visible');\n });\n cy.getByTestId(pkg.installNpm).should('contain.text', pkgName);\n });\n\n it('should render the sidebar keywords from the published manifest', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n // publishPackage writes `keywords: ['verdaccio', 'e2e', 'test']`\n // into the generated package.json.\n // the sidebar scrolls; a taller install box can push the keywords below the fold\n cy.getByTestId(pkg.keywordList).scrollIntoView().should('be.visible');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'verdaccio');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'e2e');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'test');\n });\n\n it('should click on dependencies tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.dependenciesTab).click();\n cy.wait(100);\n cy.getByTestId(pkg.dependencies).should('have.length', 1);\n\n // The dep Chip uses the dep name as its data-testid (dynamic\n // Verdaccio convention, see DependencyBlock.tsx:68), and its\n // label is `\"${name}: ${version}\"` via the `dependencies.\n // dependency-block` i18n key. Assert BOTH fields are rendered.\n cy.getByTestId(depName)\n .should('be.visible')\n .and('contain.text', depName)\n .and('contain.text', depVersion);\n\n // Also verify the Chip text matches the exact \"name: version\"\n // format so a regression in the label template would fail here\n // rather than pass via a loose substring match.\n cy.getByTestId(depName)\n .invoke('text')\n .should('match', new RegExp(`${depName}\\\\s*:\\\\s*${depVersion}`));\n });\n\n it('should click on versions tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.versionsTab).click();\n // With `unique: true` the version becomes `1.0.0-t<timestamp>`,\n // but \"1.0.0\" still appears as a substring — match loosely.\n cy.getByTestId(pkg.tagLatest)\n .children()\n .invoke('text')\n .should('match', /1\\.0\\.0/);\n });\n\n it('should click on uplinks tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.uplinksTab).click();\n cy.getByTestId(pkg.noUplinks).should('be.visible');\n });\n\n // ── Action-bar FABs: tarball download + raw viewer ─────────────\n // Both buttons live in the sidebar ActionBar. They're gated on\n // `web.showDownloadTarball` / `web.showRaw` (both default to true\n // in the ui-theme's AppConfigurationProvider) and each test is\n // also guarded by a feature flag so branches that ship a\n // different action bar can skip cleanly.\n\n maybeIt(features.publish.downloadTarball)(\n 'should fetch the tarball when the download button is clicked',\n () => {\n // The download provider hits the package manifest's dist.tarball\n // URL directly. For our published fixture the filename looks\n // like `pkg-scoped-1.0.0-t<ts>.tgz`, served from\n // `/<pkg>/-/<filename>.tgz`. Intercept before clicking.\n cy.intercept('GET', '**/pkg-scoped-*.tgz').as('tarballFetch');\n\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n cy.getByTestId(pkg.downloadTarballBtn).should('be.visible').click();\n\n // The fetch should fire and return 200. We can't assert on the\n // actual file landing on disk — Cypress doesn't track OS-level\n // downloads — but a successful GET proves the end-to-end path\n // from click → download provider → registry.\n cy.wait('@tarballFetch', { timeout: 10000 }).its('response.statusCode').should('eq', 200);\n }\n );\n\n maybeIt(features.publish.rawViewer)(\n 'should open the raw manifest viewer when the raw button is clicked',\n () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n // RawViewer is a full-screen MUI Dialog — initially unmounted\n // because `isOpen=false` keeps Dialog closed and Cypress won't\n // find it. Clicking the FAB flips `isOpen` to true.\n cy.getByTestId(pkg.rawBtn).should('be.visible').click();\n\n cy.getByTestId(pkg.rawViewerDialog, { timeout: 5000 }).should('be.visible');\n // The ReactJson viewer renders the package manifest — the\n // package name should appear somewhere in the serialized JSON.\n cy.getByTestId(pkg.rawViewerDialog).should('contain.text', pkgName);\n\n // Close via the X button and confirm the dialog goes away so\n // subsequent tests don't inherit an open overlay.\n cy.getByTestId(pkg.closeRawViewer).click();\n cy.getByTestId(pkg.rawViewerDialog).should('not.exist');\n }\n );\n });\n\n describe('private package tarball downloads', () => {\n const privatePkgName = '@private/tarball-fixture';\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', '**/-/verdaccio/data/sidebar/@private/tarball-fixture*').as('sidebar');\n\n cy.task('publishPackage', {\n pkgName: privatePkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce('private-tarball-suite');\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName: privatePkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.publish.privateDownloadTarball)(\n 'should fetch a private tarball from the home package list',\n () => {\n cy.intercept('GET', '**/tarball-fixture-*.tgz').as('privateTarballFetch');\n\n cy.wait('@pkgs');\n cy.contains(`[data-testid=\"${pkg.title}\"]`, privatePkgName)\n .should('be.visible')\n .closest(`[data-testid=\"${pkg.itemList}\"]`)\n .find(`[data-testid=\"${pkg.downloadTarball}\"]`)\n .should('be.visible')\n .click();\n\n cy.wait('@privateTarballFetch', { timeout: 10000 })\n .its('response.statusCode')\n .should('eq', 200);\n }\n );\n\n maybeIt(features.publish.privateDownloadTarball)(\n 'should fetch a private tarball from the package sidebar',\n () => {\n cy.intercept('GET', '**/tarball-fixture-*.tgz').as('privateTarballFetch');\n\n cy.wait('@pkgs');\n cy.contains(`[data-testid=\"${pkg.title}\"]`, privatePkgName).click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.downloadTarballBtn).should('be.visible').click();\n\n cy.wait('@privateTarballFetch', { timeout: 10000 })\n .its('response.statusCode')\n .should('eq', 200);\n }\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { maybeIt } from "../features.js";
|
|
2
|
+
//#region src/tests/session.ts
|
|
3
|
+
/**
|
|
4
|
+
* Session lifecycle tests around the token stored in localStorage.
|
|
5
|
+
*
|
|
6
|
+
* A JWT that expired must never leave the UI half-logged-in: the header
|
|
7
|
+
* greeting a user whose token the server treats as anonymous was the
|
|
8
|
+
* "private packages vanish but I look logged in" bug fixed in
|
|
9
|
+
* verdaccio/verdaccio#6210.
|
|
10
|
+
*/
|
|
11
|
+
/** Unsigned JWT-shaped token with the given exp (seconds since epoch). */
|
|
12
|
+
function fakeJwt(exp) {
|
|
13
|
+
const b64 = (obj) => btoa(JSON.stringify(obj)).replace(/=+$/, "");
|
|
14
|
+
return `${b64({
|
|
15
|
+
alg: "none",
|
|
16
|
+
typ: "JWT"
|
|
17
|
+
})}.${b64({
|
|
18
|
+
name: "e2e-expired",
|
|
19
|
+
exp
|
|
20
|
+
})}.sig`;
|
|
21
|
+
}
|
|
22
|
+
function sessionTests(config) {
|
|
23
|
+
const { features } = config;
|
|
24
|
+
const { header } = config.testIds;
|
|
25
|
+
describe("session token lifecycle", () => {
|
|
26
|
+
const visitWithExpiredToken = () => {
|
|
27
|
+
const expired = fakeJwt(Math.floor(Date.now() / 1e3) - 3600);
|
|
28
|
+
cy.visit(config.registryUrl, { onBeforeLoad(win) {
|
|
29
|
+
win.localStorage.setItem("username", "e2e-expired");
|
|
30
|
+
win.localStorage.setItem("token", expired);
|
|
31
|
+
} });
|
|
32
|
+
cy.getByTestId(header.container).should("be.visible");
|
|
33
|
+
};
|
|
34
|
+
maybeIt(features.session.expiredTokenLoggedOut)("an expired stored token must boot the UI logged out", () => {
|
|
35
|
+
visitWithExpiredToken();
|
|
36
|
+
cy.getByTestId(header.loginButton).should("be.visible");
|
|
37
|
+
cy.getByTestId(header.logInDialogIcon).should("not.exist");
|
|
38
|
+
});
|
|
39
|
+
maybeIt(features.session.expiredTokenPurged)("an expired stored token must also be purged from localStorage", () => {
|
|
40
|
+
visitWithExpiredToken();
|
|
41
|
+
cy.getByTestId(header.loginButton).should("be.visible");
|
|
42
|
+
cy.window().its("localStorage").invoke("getItem", "token").should("be.null");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { sessionTests };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","names":[],"sources":["../../../src/tests/session.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Session lifecycle tests around the token stored in localStorage.\n *\n * A JWT that expired must never leave the UI half-logged-in: the header\n * greeting a user whose token the server treats as anonymous was the\n * \"private packages vanish but I look logged in\" bug fixed in\n * verdaccio/verdaccio#6210.\n */\n\n/** Unsigned JWT-shaped token with the given exp (seconds since epoch). */\nfunction fakeJwt(exp: number): string {\n const b64 = (obj: unknown) => btoa(JSON.stringify(obj)).replace(/=+$/, '');\n return `${b64({ alg: 'none', typ: 'JWT' })}.${b64({ name: 'e2e-expired', exp })}.sig`;\n}\n\nexport function sessionTests(config: RegistryConfig) {\n const { features } = config;\n const { header } = config.testIds;\n\n describe('session token lifecycle', () => {\n const visitWithExpiredToken = () => {\n const expired = fakeJwt(Math.floor(Date.now() / 1000) - 3600);\n cy.visit(config.registryUrl, {\n onBeforeLoad(win) {\n win.localStorage.setItem('username', 'e2e-expired');\n win.localStorage.setItem('token', expired);\n },\n });\n cy.getByTestId(header.container).should('be.visible');\n };\n\n maybeIt(features.session.expiredTokenLoggedOut)(\n 'an expired stored token must boot the UI logged out',\n () => {\n visitWithExpiredToken();\n\n // login button visible, no greeting for the stale user\n cy.getByTestId(header.loginButton).should('be.visible');\n cy.getByTestId(header.logInDialogIcon).should('not.exist');\n }\n );\n\n maybeIt(features.session.expiredTokenPurged)(\n 'an expired stored token must also be purged from localStorage',\n () => {\n visitWithExpiredToken();\n cy.getByTestId(header.loginButton).should('be.visible');\n\n // otherwise the api client keeps attaching the stale bearer and\n // the server quietly treats every request as anonymous\n cy.window().its('localStorage').invoke('getItem', 'token').should('be.null');\n }\n );\n });\n}\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,QAAQ,KAAqB;CACpC,MAAM,OAAO,QAAiB,KAAK,KAAK,UAAU,GAAG,CAAC,CAAC,CAAC,QAAQ,OAAO,EAAE;CACzE,OAAO,GAAG,IAAI;EAAE,KAAK;EAAQ,KAAK;CAAM,CAAC,EAAE,GAAG,IAAI;EAAE,MAAM;EAAe;CAAI,CAAC,EAAE;AAClF;AAEA,SAAgB,aAAa,QAAwB;CACnD,MAAM,EAAE,aAAa;CACrB,MAAM,EAAE,WAAW,OAAO;CAE1B,SAAS,iCAAiC;EACxC,MAAM,8BAA8B;GAClC,MAAM,UAAU,QAAQ,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,IAAI;GAC5D,GAAG,MAAM,OAAO,aAAa,EAC3B,aAAa,KAAK;IAChB,IAAI,aAAa,QAAQ,YAAY,aAAa;IAClD,IAAI,aAAa,QAAQ,SAAS,OAAO;GAC3C,EACF,CAAC;GACD,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY;EACtD;EAEA,QAAQ,SAAS,QAAQ,qBAAqB,CAAC,CAC7C,6DACM;GACJ,sBAAsB;GAGtB,GAAG,YAAY,OAAO,WAAW,CAAC,CAAC,OAAO,YAAY;GACtD,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,OAAO,WAAW;EAC3D,CACF;EAEA,QAAQ,SAAS,QAAQ,kBAAkB,CAAC,CAC1C,uEACM;GACJ,sBAAsB;GACtB,GAAG,YAAY,OAAO,WAAW,CAAC,CAAC,OAAO,YAAY;GAItD,GAAG,OAAO,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,OAAO,WAAW,OAAO,CAAC,CAAC,OAAO,SAAS;EAC7E,CACF;CACF,CAAC;AACH"}
|
|
@@ -33,7 +33,7 @@ function settingsTests(config) {
|
|
|
33
33
|
it("should show both Package Managers and Translations tabs", () => {
|
|
34
34
|
cy.getByTestId(header.settingsTooltip).click();
|
|
35
35
|
cy.get("[role=\"dialog\"]").should("be.visible");
|
|
36
|
-
cy.get("[role=\"dialog\"] [role=\"tab\"]").should("have.length", 2);
|
|
36
|
+
cy.get("[role=\"dialog\"] [role=\"tab\"]").should("have.length.at.least", 2);
|
|
37
37
|
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").should("be.visible");
|
|
38
38
|
});
|
|
39
39
|
it("should switch to the Translations tab and list languages", () => {
|
|
@@ -49,7 +49,10 @@ function settingsTests(config) {
|
|
|
49
49
|
cy.get("[role=\"dialog\"]").should("be.visible");
|
|
50
50
|
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").click();
|
|
51
51
|
cy.contains("[role=\"dialog\"]", "Configuration").invoke("text").then((englishTitle) => {
|
|
52
|
-
cy.contains("[role=\"dialog\"] .MuiCard-root", "German").
|
|
52
|
+
cy.contains("[role=\"dialog\"] .MuiCard-root", "German").then(($card) => {
|
|
53
|
+
const $area = $card.find(".MuiCardActionArea-root");
|
|
54
|
+
cy.wrap($area.length ? $area : $card).click({ force: true });
|
|
55
|
+
});
|
|
53
56
|
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").should("not.exist");
|
|
54
57
|
cy.contains("[role=\"dialog\"]", englishTitle).should("not.exist");
|
|
55
58
|
});
|
|
@@ -60,6 +63,34 @@ function settingsTests(config) {
|
|
|
60
63
|
cy.get("body").type("{esc}");
|
|
61
64
|
cy.get("[role=\"dialog\"]").should("not.exist");
|
|
62
65
|
});
|
|
66
|
+
maybeIt(features.settings.localizedDates)("relative dates must follow the selected language (dayjs locale)", () => {
|
|
67
|
+
const pkgName = "@verdaccio/dates-fixture";
|
|
68
|
+
let tempFolder = null;
|
|
69
|
+
cy.task("publishPackage", {
|
|
70
|
+
pkgName,
|
|
71
|
+
version: "1.0.0",
|
|
72
|
+
unique: true
|
|
73
|
+
}).then((result) => {
|
|
74
|
+
tempFolder = result?.tempFolder ?? null;
|
|
75
|
+
});
|
|
76
|
+
cy.getByTestId(header.settingsTooltip).click();
|
|
77
|
+
cy.get("[role=\"dialog\"]").should("be.visible");
|
|
78
|
+
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").click();
|
|
79
|
+
cy.contains("[role=\"dialog\"] .MuiCard-root", "German").click({ force: true });
|
|
80
|
+
cy.get("body").type("{esc}");
|
|
81
|
+
const sidebar = `[data-testid="${config.testIds.package.sidebar}"]`;
|
|
82
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
83
|
+
cy.get(sidebar, { timeout: 1e4 }).should("be.visible");
|
|
84
|
+
cy.get(sidebar).contains(/vor /);
|
|
85
|
+
cy.get(sidebar).contains(/ago\b/).should("not.exist");
|
|
86
|
+
cy.task("unpublishPackage", {
|
|
87
|
+
pkgName,
|
|
88
|
+
tempFolder: tempFolder ?? void 0
|
|
89
|
+
});
|
|
90
|
+
cy.then(() => {
|
|
91
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
63
94
|
});
|
|
64
95
|
}
|
|
65
96
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","names":[],"sources":["../../../src/tests/settings.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the header Settings dialog and the Translations (language)\n * picker inside it.\n *\n * Requires `web.showSettings: true` in the registry config — the gear\n * icon only renders when the flag is on (see HeaderRight in Verdaccio's\n * ui-components package).\n *\n * Selector strategy: the language cards in LanguageSwitch do NOT have\n * data-testids, so this suite leans on:\n * - MUI's ARIA roles: `[role=\"dialog\"]`, `[role=\"tab\"]`\n * - The stable English labels \"Configuration\", \"Translations\",\n * \"English\", \"German\", and the language description text, which are\n * bundled into the ui-theme via `@verdaccio/ui-i18n`\n * - The settings trigger's testid from Verdaccio's HeaderToolTipIcon:\n * `header--tooltip-settings`\n */\nexport function settingsTests(config: RegistryConfig) {\n const { header } = config.testIds;\n const { features } = config;\n\n describe('settings & language', () => {\n beforeEach(() => {\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should open the settings dialog from the header', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n // Dialog title key: dialog.settings.title → \"Configuration\" in EN.\n cy.contains('[role=\"dialog\"]', 'Configuration').should('be.visible');\n });\n\n it('should show both Package Managers and Translations tabs', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.get('[role=\"dialog\"] [role=\"tab\"]').should('have.length', 2);\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should('be.visible');\n });\n\n it('should switch to the Translations tab and list languages', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n // language.description text is bundled in EN — good sentinel.\n cy.contains('[role=\"dialog\"]', /this is the configuration details for the language/i).should(\n 'be.visible'\n );\n // Sanity-check a couple of language cards are rendered.\n cy.contains('[role=\"dialog\"]', 'English').should('be.visible');\n cy.contains('[role=\"dialog\"]', 'German').should('be.visible');\n });\n\n maybeIt(features.settings.languageSwitcher)(\n 'should change the UI language when a language card is clicked',\n () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n\n // Before switching, capture the current dialog title so we can\n // assert it changes. This avoids hard-coding the German string.\n cy.contains('[role=\"dialog\"]', 'Configuration')\n .invoke('text')\n .then((englishTitle) => {\n // Click the German card. We scope to the dialog + the MUI\n // card class to avoid matching other \"German\" text.\n cy.contains('[role=\"dialog\"] .MuiCard-root', 'German').
|
|
1
|
+
{"version":3,"file":"settings.js","names":[],"sources":["../../../src/tests/settings.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the header Settings dialog and the Translations (language)\n * picker inside it.\n *\n * Requires `web.showSettings: true` in the registry config — the gear\n * icon only renders when the flag is on (see HeaderRight in Verdaccio's\n * ui-components package).\n *\n * Selector strategy: the language cards in LanguageSwitch do NOT have\n * data-testids, so this suite leans on:\n * - MUI's ARIA roles: `[role=\"dialog\"]`, `[role=\"tab\"]`\n * - The stable English labels \"Configuration\", \"Translations\",\n * \"English\", \"German\", and the language description text, which are\n * bundled into the ui-theme via `@verdaccio/ui-i18n`\n * - The settings trigger's testid from Verdaccio's HeaderToolTipIcon:\n * `header--tooltip-settings`\n */\nexport function settingsTests(config: RegistryConfig) {\n const { header } = config.testIds;\n const { features } = config;\n\n describe('settings & language', () => {\n beforeEach(() => {\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should open the settings dialog from the header', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n // Dialog title key: dialog.settings.title → \"Configuration\" in EN.\n cy.contains('[role=\"dialog\"]', 'Configuration').should('be.visible');\n });\n\n it('should show both Package Managers and Translations tabs', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n // 9.x adds a \"Publishing\" tab, so count the two this test is about as a minimum\n cy.get('[role=\"dialog\"] [role=\"tab\"]').should('have.length.at.least', 2);\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should('be.visible');\n });\n\n it('should switch to the Translations tab and list languages', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n // language.description text is bundled in EN — good sentinel.\n cy.contains('[role=\"dialog\"]', /this is the configuration details for the language/i).should(\n 'be.visible'\n );\n // Sanity-check a couple of language cards are rendered.\n cy.contains('[role=\"dialog\"]', 'English').should('be.visible');\n cy.contains('[role=\"dialog\"]', 'German').should('be.visible');\n });\n\n maybeIt(features.settings.languageSwitcher)(\n 'should change the UI language when a language card is clicked',\n () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n\n // Before switching, capture the current dialog title so we can\n // assert it changes. This avoids hard-coding the German string.\n cy.contains('[role=\"dialog\"]', 'Configuration')\n .invoke('text')\n .then((englishTitle) => {\n // Click the German card. We scope to the dialog + the MUI\n // card class to avoid matching other \"German\" text.\n // released UIs handle the click on the card, 9.x on its CardActionArea\n cy.contains('[role=\"dialog\"] .MuiCard-root', 'German').then(($card) => {\n const $area = $card.find('.MuiCardActionArea-root');\n cy.wrap($area.length ? $area : $card).click({ force: true });\n });\n\n // The English tab label \"Translations\" must no longer be\n // present anywhere in the dialog — strongest proof the\n // language actually switched.\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should('not.exist');\n // And the original dialog title text should also be gone.\n cy.contains('[role=\"dialog\"]', englishTitle).should('not.exist');\n });\n }\n );\n\n it('should close the settings dialog with Escape', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.get('body').type('{esc}');\n cy.get('[role=\"dialog\"]').should('not.exist');\n });\n\n maybeIt(features.settings.localizedDates)(\n 'relative dates must follow the selected language (dayjs locale)',\n () => {\n // fresh fixture so \"published … ago\" is a relative date\n const pkgName = '@verdaccio/dates-fixture';\n let tempFolder: string | null = null;\n cy.task('publishPackage', { pkgName, version: '1.0.0', unique: true }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n // switch the UI to German via the settings dialog\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n cy.contains('[role=\"dialog\"] .MuiCard-root', 'German').click({ force: true });\n cy.get('body').type('{esc}');\n\n // the language persists; a fresh page load must localize dayjs too\n const sidebar = `[data-testid=\"${config.testIds.package.sidebar}\"]`;\n cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);\n cy.get(sidebar, { timeout: 10000 }).should('be.visible');\n // dayjs de locale renders relative times as \"vor ein paar Sekunden\";\n // the released UIs kept English (\"a few seconds ago\") in every language\n cy.get(sidebar).contains(/vor /);\n cy.get(sidebar).contains(/ago\\b/).should('not.exist');\n\n cy.task('unpublishPackage', { pkgName, tempFolder: tempFolder ?? undefined });\n cy.then(() => {\n if (tempFolder) cy.task('cleanupPublished', tempFolder);\n });\n }\n );\n });\n}\n"],"mappings":"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"}
|
package/build/features.d.ts
CHANGED
|
@@ -46,6 +46,13 @@ export interface Features {
|
|
|
46
46
|
* builds that lag behind the upstream translation file.
|
|
47
47
|
*/
|
|
48
48
|
languageSwitcher: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether to assert that dates follow the selected language
|
|
51
|
+
* (dayjs locale). Released UIs never load the dayjs locale, so
|
|
52
|
+
* relative dates stay English in every language (fixed in
|
|
53
|
+
* verdaccio/verdaccio#6210) — probe.
|
|
54
|
+
*/
|
|
55
|
+
localizedDates: boolean;
|
|
49
56
|
};
|
|
50
57
|
signin: {
|
|
51
58
|
/**
|
|
@@ -108,6 +115,47 @@ export interface Features {
|
|
|
108
115
|
* `latest`, so this defaults to `false` as a probe.
|
|
109
116
|
*/
|
|
110
117
|
versionNotFound: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Whether to run the "versions tab shows the CURRENT package after
|
|
120
|
+
* SPA navigation" test. The detail routes reuse one mounted
|
|
121
|
+
* component, and released UIs cache the previous package's versions
|
|
122
|
+
* in local state (fixed in verdaccio/verdaccio#6210) — defaults to
|
|
123
|
+
* `false` as a probe.
|
|
124
|
+
*/
|
|
125
|
+
staleVersionsNavigation: boolean;
|
|
126
|
+
};
|
|
127
|
+
manifestRendering: {
|
|
128
|
+
/**
|
|
129
|
+
* Whether to run the string-form manifest tests: `repository`,
|
|
130
|
+
* `funding` and `bugs` in their string/array forms are valid npm
|
|
131
|
+
* and must render (fixed in verdaccio/verdaccio#6210) — probe.
|
|
132
|
+
*/
|
|
133
|
+
stringForms: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Whether to assert gravatar avatars render on the detail sidebar.
|
|
136
|
+
* Released UIs read `avatar` while the sidebar endpoint sends
|
|
137
|
+
* `_avatar` (fixed in verdaccio/verdaccio#6210) — probe.
|
|
138
|
+
*/
|
|
139
|
+
gravatarAvatars: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Whether to assert contributors without email don't collapse into
|
|
142
|
+
* one (fixed in verdaccio/verdaccio#6210) — probe.
|
|
143
|
+
*/
|
|
144
|
+
developersWithoutEmail: boolean;
|
|
145
|
+
};
|
|
146
|
+
session: {
|
|
147
|
+
/**
|
|
148
|
+
* Whether to assert that an expired token in localStorage boots the
|
|
149
|
+
* UI logged out (header shows the login button). Holds on every
|
|
150
|
+
* released line.
|
|
151
|
+
*/
|
|
152
|
+
expiredTokenLoggedOut: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Whether to assert the expired token is also PURGED from
|
|
155
|
+
* localStorage on boot — otherwise the api client keeps attaching
|
|
156
|
+
* it (fixed in verdaccio/verdaccio#6210) — probe.
|
|
157
|
+
*/
|
|
158
|
+
expiredTokenPurged: boolean;
|
|
111
159
|
};
|
|
112
160
|
failureModes: {
|
|
113
161
|
/**
|
|
@@ -131,6 +179,18 @@ export interface Features {
|
|
|
131
179
|
* so this defaults to `false` as a probe.
|
|
132
180
|
*/
|
|
133
181
|
detailErrorState: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Whether to run the "search 5xx shows an error, not 'no results
|
|
184
|
+
* found'" test. The error state ships with verdaccio/verdaccio#6210
|
|
185
|
+
* — probe.
|
|
186
|
+
*/
|
|
187
|
+
searchError: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Whether to run the "failed tarball download shows an error
|
|
190
|
+
* snackbar" test — clicking download and having nothing happen sent
|
|
191
|
+
* users retrying blindly (fixed in verdaccio/verdaccio#6210) — probe.
|
|
192
|
+
*/
|
|
193
|
+
downloadError: boolean;
|
|
134
194
|
};
|
|
135
195
|
i18n: {
|
|
136
196
|
/**
|
|
@@ -166,6 +226,51 @@ export interface Features {
|
|
|
166
226
|
*/
|
|
167
227
|
validation: boolean;
|
|
168
228
|
};
|
|
229
|
+
homeDetailUx: {
|
|
230
|
+
/**
|
|
231
|
+
* Whether to run the home toolbar tests: filter narrows the list and
|
|
232
|
+
* lives in `?q=`, `?scope=` restores the scope, no-match message.
|
|
233
|
+
* Ships with the 9.x home toolbar — probe, defaults to `false`.
|
|
234
|
+
*/
|
|
235
|
+
homeToolbar: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Whether to assert the package page tab lives in `?tab=` (click
|
|
238
|
+
* writes it, a link opens it, readme removes it) — probe.
|
|
239
|
+
*/
|
|
240
|
+
detailTabInUrl: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* Whether to assert the step-by-step welcome card (setup tabs per
|
|
243
|
+
* package manager, `.npmrc` tab, uplinks note) — probe.
|
|
244
|
+
*/
|
|
245
|
+
welcomeGuide: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Whether to assert the sidebar install box lists `bun` and the
|
|
248
|
+
* `.npmrc` registry line. Needs `bun` in `web.pkgManagers` (default
|
|
249
|
+
* on 9.x) — probe.
|
|
250
|
+
*/
|
|
251
|
+
installBunAndNpmrc: boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Whether to assert that a copy button confirms the copy (its
|
|
254
|
+
* accessible name switches to "Copied") — probe.
|
|
255
|
+
*/
|
|
256
|
+
copyFeedback: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Whether to assert search results are grouped by origin and a
|
|
259
|
+
* locally published package lands in the "published here" group.
|
|
260
|
+
* Needs `web.showUplinks` (default true) — probe.
|
|
261
|
+
*/
|
|
262
|
+
searchGrouping: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Whether to assert that the version on each home list item links
|
|
265
|
+
* to its version page `/-/web/detail/<name>/v/<version>` — probe.
|
|
266
|
+
*/
|
|
267
|
+
versionLink: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Whether to assert the settings dialog has a "Publishing" tab with
|
|
270
|
+
* the welcome guide, so it stays reachable once packages exist — probe.
|
|
271
|
+
*/
|
|
272
|
+
settingsPublishTab: boolean;
|
|
273
|
+
};
|
|
169
274
|
changePassword: {
|
|
170
275
|
/**
|
|
171
276
|
* Whether to run the happy-path test (submit valid change,
|
package/build/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { Features } from './features';
|
|
|
6
6
|
export { DEFAULT_SELECTORS, DEFAULT_TEST_IDS } from './testIds';
|
|
7
7
|
export { DEFAULT_FEATURES, maybeIt } from './features';
|
|
8
8
|
export { publishPackage, cleanupPublished, unpublishPackage } from './tasks';
|
|
9
|
-
export { homeTests, signinTests, publishTests, searchTests, settingsTests, layoutTests, changePasswordTests, detailTests, failureModeTests, i18nKeyTests, signupTests, } from './tests';
|
|
9
|
+
export { homeTests, signinTests, publishTests, searchTests, settingsTests, layoutTests, changePasswordTests, detailTests, failureModeTests, i18nKeyTests, manifestRenderingTests, sessionTests, signupTests, homeDetailUxTests, } from './tests';
|
|
10
10
|
/**
|
|
11
11
|
* Build a full RegistryConfig from user-provided options with defaults.
|
|
12
12
|
*
|
package/build/tasks/publish.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface PublishPackageInput {
|
|
|
8
8
|
};
|
|
9
9
|
dependencies?: Record<string, string>;
|
|
10
10
|
devDependencies?: Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Extra fields merged into the generated package.json AFTER the
|
|
13
|
+
* defaults, so fixtures can carry arbitrary manifest shapes: a string
|
|
14
|
+
* `repository`, a `funding` array, `contributors` without email, a
|
|
15
|
+
* non-string `homepage`… — exactly the field forms npm accepts on
|
|
16
|
+
* publish but UIs tend to mishandle. `name`, `version` and
|
|
17
|
+
* `publishConfig` always win over this override.
|
|
18
|
+
*/
|
|
19
|
+
manifest?: Record<string, unknown>;
|
|
11
20
|
/**
|
|
12
21
|
* When true, append a timestamp suffix to the version so reruns against
|
|
13
22
|
* a persistent registry don't collide on 403. The suffix is a valid
|
package/build/testIds.d.ts
CHANGED
|
@@ -21,6 +21,20 @@ export interface TestIds {
|
|
|
21
21
|
helpCard: string;
|
|
22
22
|
/** 404 "not found" container. */
|
|
23
23
|
notFound: string;
|
|
24
|
+
/** Filter / scope / sort bar above the package list (9.x). */
|
|
25
|
+
packageListToolbar: string;
|
|
26
|
+
/** Text filter input inside the toolbar. */
|
|
27
|
+
packageListFilter: string;
|
|
28
|
+
/** Native input of the scope select (only with more than one scope). */
|
|
29
|
+
packageListScope: string;
|
|
30
|
+
/** Message shown when the filter matches no package. */
|
|
31
|
+
packageListNoMatch: string;
|
|
32
|
+
/** Prefix of the welcome card setup tabs: `<prefix><manager>`. */
|
|
33
|
+
helpSetupTabPrefix: string;
|
|
34
|
+
/** Note on the welcome card explaining cached uplink packages. */
|
|
35
|
+
helpUplinksNote: string;
|
|
36
|
+
/** Step list of the publishing guide, in the welcome card and the settings (9.x). */
|
|
37
|
+
helpGuide: string;
|
|
24
38
|
};
|
|
25
39
|
header: {
|
|
26
40
|
/** Outermost `<NavBar>` element. */
|
|
@@ -39,6 +53,10 @@ export interface TestIds {
|
|
|
39
53
|
loginButton: string;
|
|
40
54
|
/** Gear icon that opens the settings dialog. */
|
|
41
55
|
settingsTooltip: string;
|
|
56
|
+
/** "Publishing" tab of the settings dialog (9.x). */
|
|
57
|
+
settingsPublishTab: string;
|
|
58
|
+
/** Panel of the "Publishing" tab (9.x). */
|
|
59
|
+
settingsPublishPanel: string;
|
|
42
60
|
/** Info icon that opens the registry info dialog. */
|
|
43
61
|
infoTooltip: string;
|
|
44
62
|
/**
|
|
@@ -79,6 +97,8 @@ export interface TestIds {
|
|
|
79
97
|
itemList: string;
|
|
80
98
|
/** Package name link in the package list (home + search results). */
|
|
81
99
|
title: string;
|
|
100
|
+
/** Version link of a package list item, to its version page (9.x). */
|
|
101
|
+
versionLink: string;
|
|
82
102
|
/** Readme container on the package detail page. */
|
|
83
103
|
readme: string;
|
|
84
104
|
/** Sidebar container on the package detail page. */
|
|
@@ -91,6 +111,14 @@ export interface TestIds {
|
|
|
91
111
|
installYarn: string;
|
|
92
112
|
/** Individual install line for pnpm. */
|
|
93
113
|
installPnpm: string;
|
|
114
|
+
/** Individual install line for bun (9.x). */
|
|
115
|
+
installBun: string;
|
|
116
|
+
/** `.npmrc` registry line below the install commands (9.x). */
|
|
117
|
+
installNpmrc: string;
|
|
118
|
+
/** Copy button of the `.npmrc` registry line. */
|
|
119
|
+
installNpmrcCopy: string;
|
|
120
|
+
/** Tab that shows the readme. */
|
|
121
|
+
readmeTab: string;
|
|
94
122
|
/** Keyword list below the install section. */
|
|
95
123
|
keywordList: string;
|
|
96
124
|
/** Tab that reveals the dependencies view. */
|
|
@@ -116,6 +144,12 @@ export interface TestIds {
|
|
|
116
144
|
/** Close button inside the raw viewer dialog. */
|
|
117
145
|
closeRawViewer: string;
|
|
118
146
|
};
|
|
147
|
+
search: {
|
|
148
|
+
/** Prefix of the origin group headers: `<prefix>private|cached|remote`. */
|
|
149
|
+
groupPrefix: string;
|
|
150
|
+
/** Prefix of the origin icon on each result: `<prefix>private|...`. */
|
|
151
|
+
originPrefix: string;
|
|
152
|
+
};
|
|
119
153
|
errors: {
|
|
120
154
|
/**
|
|
121
155
|
* Generic error page shown when a data request fails without a
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RegistryConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Home and package page UX that ships with the 9.x web UI: the package
|
|
4
|
+
* list toolbar, the tab in the address, the welcome guide, the install
|
|
5
|
+
* box (bun and `.npmrc`), copy feedback and search grouped by origin.
|
|
6
|
+
*
|
|
7
|
+
* Every test is a probe (flag defaults to `false`): released lines do
|
|
8
|
+
* not have these features yet.
|
|
9
|
+
*/
|
|
10
|
+
export declare function homeDetailUxTests(config: RegistryConfig): void;
|
package/build/tests/index.d.ts
CHANGED
|
@@ -9,3 +9,6 @@ export { detailTests } from './detail';
|
|
|
9
9
|
export { failureModeTests } from './failure-modes';
|
|
10
10
|
export { i18nKeyTests } from './i18n-keys';
|
|
11
11
|
export { signupTests } from './signup';
|
|
12
|
+
export { manifestRenderingTests } from './manifest-rendering';
|
|
13
|
+
export { sessionTests } from './session';
|
|
14
|
+
export { homeDetailUxTests } from './home-detail-ux';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RegistryConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Manifest-shape rendering tests.
|
|
4
|
+
*
|
|
5
|
+
* npm does not validate these fields on publish: `repository`, `funding`
|
|
6
|
+
* and `bugs` are all valid in string form, `funding` also as an array,
|
|
7
|
+
* and contributors commonly ship without an email. Real packuments carry
|
|
8
|
+
* all of these — the UI must render them instead of silently dropping
|
|
9
|
+
* whole sections (the bug family fixed in verdaccio/verdaccio#6210,
|
|
10
|
+
* where every probe here failed).
|
|
11
|
+
*
|
|
12
|
+
* Requires the `manifest` override support in the publishPackage task.
|
|
13
|
+
*/
|
|
14
|
+
export declare function manifestRenderingTests(config: RegistryConfig): void;
|