@verdaccio/e2e-ui 2.5.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 +58 -1
- package/build/cjs/features.cjs.map +1 -1
- package/build/cjs/index.cjs +20 -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 +16 -1
- package/build/cjs/testIds.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 +106 -0
- package/build/cjs/tests/detail.cjs.map +1 -0
- package/build/cjs/tests/failure-modes.cjs +97 -0
- package/build/cjs/tests/failure-modes.cjs.map +1 -0
- package/build/cjs/tests/i18n-keys.cjs +74 -0
- package/build/cjs/tests/i18n-keys.cjs.map +1 -0
- package/build/cjs/tests/index.cjs +26 -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/cjs/tests/signup.cjs +56 -0
- package/build/cjs/tests/signup.cjs.map +1 -0
- package/build/esm/features.js +58 -1
- package/build/esm/features.js.map +1 -1
- package/build/esm/index.js +15 -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 +16 -1
- package/build/esm/testIds.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 +106 -0
- package/build/esm/tests/detail.js.map +1 -0
- package/build/esm/tests/failure-modes.js +97 -0
- package/build/esm/tests/failure-modes.js.map +1 -0
- package/build/esm/tests/i18n-keys.js +74 -0
- package/build/esm/tests/i18n-keys.js.map +1 -0
- package/build/esm/tests/index.js +14 -0
- 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/esm/tests/signup.js +56 -0
- package/build/esm/tests/signup.js.map +1 -0
- package/build/features.d.ts +139 -0
- package/build/index.d.ts +1 -1
- package/build/tasks/publish.d.ts +9 -0
- package/build/testIds.d.ts +18 -0
- package/build/tests/detail.d.ts +12 -0
- package/build/tests/failure-modes.d.ts +11 -0
- package/build/tests/i18n-keys.d.ts +2 -0
- package/build/tests/index.d.ts +6 -0
- package/build/tests/manifest-rendering.d.ts +14 -0
- package/build/tests/session.d.ts +2 -0
- package/build/tests/signup.d.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
2
|
+
//#region src/tests/detail.ts
|
|
3
|
+
/**
|
|
4
|
+
* Package detail page tests: version-pinned URLs and the wire format of
|
|
5
|
+
* the UI's data requests.
|
|
6
|
+
*
|
|
7
|
+
* The wire format is an observable contract, not an implementation
|
|
8
|
+
* detail: proxies, intercepts and every npm client expect scoped names
|
|
9
|
+
* as literal `/@scope/name`. A ui-components refactor that
|
|
10
|
+
* percent-encoded the `@` broke exactly these suites
|
|
11
|
+
* (verdaccio/verdaccio#6210), which is why it is pinned here on purpose.
|
|
12
|
+
*/
|
|
13
|
+
function detailTests(config) {
|
|
14
|
+
const { features } = config;
|
|
15
|
+
const { package: pkg, home } = config.testIds;
|
|
16
|
+
describe("package detail", () => {
|
|
17
|
+
describe("with a published package", () => {
|
|
18
|
+
const pkgName = "@verdaccio/detail-fixture";
|
|
19
|
+
let tempFolder = null;
|
|
20
|
+
let version = "1.0.0";
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
cy.task("publishPackage", {
|
|
23
|
+
pkgName,
|
|
24
|
+
version: "1.0.0",
|
|
25
|
+
unique: true
|
|
26
|
+
}).then((result) => {
|
|
27
|
+
tempFolder = result?.tempFolder ?? null;
|
|
28
|
+
version = result?.version ?? "1.0.0";
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
cy.task("unpublishPackage", {
|
|
33
|
+
pkgName,
|
|
34
|
+
tempFolder: tempFolder ?? void 0
|
|
35
|
+
});
|
|
36
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
37
|
+
tempFolder = null;
|
|
38
|
+
});
|
|
39
|
+
require_features.maybeIt(features.detail.scopedWireFormat)("should request sidebar and readme with the scope literal (@, not %40)", () => {
|
|
40
|
+
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`).as("sidebar");
|
|
41
|
+
cy.intercept("GET", `/-/verdaccio/data/package/readme/${pkgName}*`).as("readme");
|
|
42
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
43
|
+
cy.wait("@sidebar", { timeout: 1e4 }).then((interception) => {
|
|
44
|
+
expect(interception.request.url).to.contain(`/sidebar/${pkgName}`);
|
|
45
|
+
expect(interception.request.url).to.not.contain("%40");
|
|
46
|
+
});
|
|
47
|
+
cy.wait("@readme", { timeout: 1e4 });
|
|
48
|
+
cy.getByTestId(pkg.sidebar).should("be.visible");
|
|
49
|
+
});
|
|
50
|
+
require_features.maybeIt(features.detail.versionPinned)("should pin the requested version via the ?v= query on /v/<version> urls", () => {
|
|
51
|
+
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`).as("sidebar");
|
|
52
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/${version}`);
|
|
53
|
+
cy.wait("@sidebar", { timeout: 1e4 }).then((interception) => {
|
|
54
|
+
expect(interception.request.url).to.contain(`v=${version}`);
|
|
55
|
+
});
|
|
56
|
+
cy.getByTestId(pkg.sidebar).should("be.visible");
|
|
57
|
+
cy.getByTestId(pkg.sidebar).contains(version);
|
|
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
|
+
});
|
|
92
|
+
require_features.maybeIt(features.detail.versionNotFound)("should render Not Found for a version that does not exist", () => {
|
|
93
|
+
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`).as("sidebar");
|
|
94
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}/v/9.9.9`);
|
|
95
|
+
cy.wait("@sidebar", { timeout: 1e4 }).then((interception) => {
|
|
96
|
+
expect(interception.response?.statusCode).to.eq(404);
|
|
97
|
+
});
|
|
98
|
+
cy.getByTestId(home.notFound, { timeout: 1e4 }).should("be.visible");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
exports.detailTests = detailTests;
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=detail.cjs.map
|
|
@@ -0,0 +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.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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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
2
|
+
//#region src/tests/failure-modes.ts
|
|
3
|
+
/**
|
|
4
|
+
* Failure-mode tests: what the UI shows when the backend misbehaves.
|
|
5
|
+
*
|
|
6
|
+
* A registry that is down must never look like a healthy empty registry
|
|
7
|
+
* (that invites the user to publish into the void), and a failed detail
|
|
8
|
+
* request must render an error state instead of a blank page. These are
|
|
9
|
+
* the failure classes fixed by verdaccio/verdaccio#6210 — the strict
|
|
10
|
+
* assertions are feature-gated probes until that ships.
|
|
11
|
+
*/
|
|
12
|
+
function failureModeTests(config) {
|
|
13
|
+
const { features } = config;
|
|
14
|
+
const { home, header, errors, package: pkg } = config.testIds;
|
|
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
|
+
});
|
|
27
|
+
require_features.maybeIt(features.failureModes.homeServerError)("a 5xx on the package list must not render the empty-registry onboarding", () => {
|
|
28
|
+
cy.intercept("GET", "/-/verdaccio/data/packages", {
|
|
29
|
+
statusCode: 500,
|
|
30
|
+
body: { error: "internal server error" }
|
|
31
|
+
}).as("pkgs");
|
|
32
|
+
cy.visit(config.registryUrl);
|
|
33
|
+
cy.wait("@pkgs", { timeout: 1e4 });
|
|
34
|
+
cy.getByTestId(header.container).should("be.visible");
|
|
35
|
+
cy.getByTestId(home.helpCard).should("not.exist");
|
|
36
|
+
});
|
|
37
|
+
require_features.maybeIt(features.failureModes.homeNetworkError)("an unreachable backend must not render the empty-registry onboarding", () => {
|
|
38
|
+
cy.intercept("GET", "/-/verdaccio/data/packages", { forceNetworkError: true }).as("pkgs");
|
|
39
|
+
cy.visit(config.registryUrl);
|
|
40
|
+
cy.wait("@pkgs", { timeout: 1e4 });
|
|
41
|
+
cy.getByTestId(header.container).should("be.visible");
|
|
42
|
+
cy.getByTestId(home.helpCard).should("not.exist");
|
|
43
|
+
cy.getByTestId(errors.genericError, { timeout: 1e4 }).should("be.visible");
|
|
44
|
+
});
|
|
45
|
+
describe("with a published package", () => {
|
|
46
|
+
const pkgName = "@verdaccio/failure-fixture";
|
|
47
|
+
let tempFolder = null;
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
cy.task("publishPackage", {
|
|
50
|
+
pkgName,
|
|
51
|
+
version: "1.0.0",
|
|
52
|
+
unique: true
|
|
53
|
+
}).then((result) => {
|
|
54
|
+
tempFolder = result?.tempFolder ?? null;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
cy.task("unpublishPackage", {
|
|
59
|
+
pkgName,
|
|
60
|
+
tempFolder: tempFolder ?? void 0
|
|
61
|
+
});
|
|
62
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
63
|
+
tempFolder = null;
|
|
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
|
+
});
|
|
76
|
+
require_features.maybeIt(features.failureModes.detailErrorState)("a 5xx on the detail data must render an error state, not a blank page", () => {
|
|
77
|
+
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}*`, {
|
|
78
|
+
statusCode: 500,
|
|
79
|
+
body: { error: "internal server error" }
|
|
80
|
+
}).as("sidebar");
|
|
81
|
+
cy.intercept("GET", `/-/verdaccio/data/package/readme/${pkgName}*`, {
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
body: { error: "internal server error" }
|
|
84
|
+
}).as("readme");
|
|
85
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
86
|
+
cy.wait("@sidebar", { timeout: 1e4 });
|
|
87
|
+
cy.getByTestId(errors.genericError, { timeout: 1e4 }).should("be.visible");
|
|
88
|
+
cy.getByTestId(header.container).should("be.visible");
|
|
89
|
+
cy.getByTestId(pkg.sidebar).should("not.exist");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
exports.failureModeTests = failureModeTests;
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=failure-modes.cjs.map
|
|
@@ -0,0 +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.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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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
2
|
+
//#region src/tests/i18n-keys.ts
|
|
3
|
+
/**
|
|
4
|
+
* Raw-i18n-key detector: no page may render untranslated keys like
|
|
5
|
+
* `security.addUser.title` as visible text.
|
|
6
|
+
*
|
|
7
|
+
* This pins the whole "namespace missing from the shipped bundle" class
|
|
8
|
+
* (the `security.*` namespace was absent from every published ui-theme
|
|
9
|
+
* until verdaccio/verdaccio#6210): a missing key makes i18next return
|
|
10
|
+
* the key itself, which this scan catches regardless of which key
|
|
11
|
+
* regressed.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Namespaces of the ui-theme translation file. Restricting the scan to
|
|
15
|
+
* known prefixes keeps false positives out (package names like
|
|
16
|
+
* `lodash.merge` or readme prose with dots must not trip it).
|
|
17
|
+
*/
|
|
18
|
+
var I18N_NAMESPACES = [
|
|
19
|
+
"security",
|
|
20
|
+
"dialog",
|
|
21
|
+
"header",
|
|
22
|
+
"sidebar",
|
|
23
|
+
"package",
|
|
24
|
+
"versions",
|
|
25
|
+
"search",
|
|
26
|
+
"form",
|
|
27
|
+
"form-placeholder",
|
|
28
|
+
"form-validation",
|
|
29
|
+
"button",
|
|
30
|
+
"error",
|
|
31
|
+
"uplinks",
|
|
32
|
+
"dependencies",
|
|
33
|
+
"autoComplete",
|
|
34
|
+
"footer",
|
|
35
|
+
"stage",
|
|
36
|
+
"help"
|
|
37
|
+
].join("|");
|
|
38
|
+
var RAW_KEY_PATTERN = new RegExp(`(?:^|[\\s>])(?:${I18N_NAMESPACES})\\.[a-zA-Z][a-zA-Z.-]+`);
|
|
39
|
+
function assertNoRawI18nKeys(context) {
|
|
40
|
+
cy.get("body").invoke("text").then((text) => {
|
|
41
|
+
const match = text.match(RAW_KEY_PATTERN);
|
|
42
|
+
expect(match, `raw i18n key rendered on ${context}: ${match?.[0] ?? ""}`).to.be.null;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function i18nKeyTests(config) {
|
|
46
|
+
const { features } = config;
|
|
47
|
+
const { header } = config.testIds;
|
|
48
|
+
describe("i18n keys", () => {
|
|
49
|
+
require_features.maybeIt(features.i18n.noRawKeysCorePages)("home page must not render raw i18n keys", () => {
|
|
50
|
+
cy.visit(config.registryUrl);
|
|
51
|
+
cy.getByTestId(header.container).should("be.visible");
|
|
52
|
+
assertNoRawI18nKeys("home");
|
|
53
|
+
});
|
|
54
|
+
require_features.maybeIt(features.i18n.noRawKeysSecurityPages)("login dialog and security pages must not render raw i18n keys", () => {
|
|
55
|
+
cy.visit(config.registryUrl);
|
|
56
|
+
cy.getByTestId(config.testIds.header.loginButton).click();
|
|
57
|
+
cy.getByTestId(config.testIds.login.dialogContent).should("be.visible");
|
|
58
|
+
assertNoRawI18nKeys("login dialog");
|
|
59
|
+
for (const path of [
|
|
60
|
+
"/-/web/login",
|
|
61
|
+
"/-/web/add-user",
|
|
62
|
+
"/-/web/change-password"
|
|
63
|
+
]) {
|
|
64
|
+
cy.visit(`${config.registryUrl}${path}`, { failOnStatusCode: false });
|
|
65
|
+
cy.get("body").should("be.visible");
|
|
66
|
+
assertNoRawI18nKeys(path);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
exports.i18nKeyTests = i18nKeyTests;
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=i18n-keys.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-keys.cjs","names":[],"sources":["../../../src/tests/i18n-keys.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Raw-i18n-key detector: no page may render untranslated keys like\n * `security.addUser.title` as visible text.\n *\n * This pins the whole \"namespace missing from the shipped bundle\" class\n * (the `security.*` namespace was absent from every published ui-theme\n * until verdaccio/verdaccio#6210): a missing key makes i18next return\n * the key itself, which this scan catches regardless of which key\n * regressed.\n */\n\n/**\n * Namespaces of the ui-theme translation file. Restricting the scan to\n * known prefixes keeps false positives out (package names like\n * `lodash.merge` or readme prose with dots must not trip it).\n */\nconst I18N_NAMESPACES = [\n 'security',\n 'dialog',\n 'header',\n 'sidebar',\n 'package',\n 'versions',\n 'search',\n 'form',\n 'form-placeholder',\n 'form-validation',\n 'button',\n 'error',\n 'uplinks',\n 'dependencies',\n 'autoComplete',\n 'footer',\n 'stage',\n 'help',\n].join('|');\n\nconst RAW_KEY_PATTERN = new RegExp(`(?:^|[\\\\s>])(?:${I18N_NAMESPACES})\\\\.[a-zA-Z][a-zA-Z.-]+`);\n\nfunction assertNoRawI18nKeys(context: string) {\n cy.get('body')\n .invoke('text')\n .then((text: string) => {\n const match = text.match(RAW_KEY_PATTERN);\n expect(match, `raw i18n key rendered on ${context}: ${match?.[0] ?? ''}`).to.be.null;\n });\n}\n\nexport function i18nKeyTests(config: RegistryConfig) {\n const { features } = config;\n const { header } = config.testIds;\n\n describe('i18n keys', () => {\n maybeIt(features.i18n.noRawKeysCorePages)('home page must not render raw i18n keys', () => {\n cy.visit(config.registryUrl);\n cy.getByTestId(header.container).should('be.visible');\n assertNoRawI18nKeys('home');\n });\n\n maybeIt(features.i18n.noRawKeysSecurityPages)(\n 'login dialog and security pages must not render raw i18n keys',\n () => {\n // login dialog\n cy.visit(config.registryUrl);\n cy.getByTestId(config.testIds.header.loginButton).click();\n cy.getByTestId(config.testIds.login.dialogContent).should('be.visible');\n assertNoRawI18nKeys('login dialog');\n\n // standalone security pages (render regardless of the `next`\n // param except /-/web/login, which needs a valid one to show\n // the form — the NotFound fallback is translated too, so the\n // scan still applies)\n for (const path of ['/-/web/login', '/-/web/add-user', '/-/web/change-password']) {\n cy.visit(`${config.registryUrl}${path}`, { failOnStatusCode: false });\n cy.get('body').should('be.visible');\n assertNoRawI18nKeys(path);\n }\n }\n );\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,IAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,GAAG;AAEV,IAAM,kBAAkB,IAAI,OAAO,kBAAkB,gBAAgB,wBAAwB;AAE7F,SAAS,oBAAoB,SAAiB;CAC5C,GAAG,IAAI,MAAM,CAAC,CACX,OAAO,MAAM,CAAC,CACd,MAAM,SAAiB;EACtB,MAAM,QAAQ,KAAK,MAAM,eAAe;EACxC,OAAO,OAAO,4BAA4B,QAAQ,IAAI,QAAQ,MAAM,IAAI,CAAC,CAAC,GAAG,GAAG;CAClF,CAAC;AACL;AAEA,SAAgB,aAAa,QAAwB;CACnD,MAAM,EAAE,aAAa;CACrB,MAAM,EAAE,WAAW,OAAO;CAE1B,SAAS,mBAAmB;EAC1B,iBAAA,QAAQ,SAAS,KAAK,kBAAkB,CAAC,CAAC,iDAAiD;GACzF,GAAG,MAAM,OAAO,WAAW;GAC3B,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,OAAO,YAAY;GACpD,oBAAoB,MAAM;EAC5B,CAAC;EAED,iBAAA,QAAQ,SAAS,KAAK,sBAAsB,CAAC,CAC3C,uEACM;GAEJ,GAAG,MAAM,OAAO,WAAW;GAC3B,GAAG,YAAY,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC,MAAM;GACxD,GAAG,YAAY,OAAO,QAAQ,MAAM,aAAa,CAAC,CAAC,OAAO,YAAY;GACtE,oBAAoB,cAAc;GAMlC,KAAK,MAAM,QAAQ;IAAC;IAAgB;IAAmB;GAAwB,GAAG;IAChF,GAAG,MAAM,GAAG,OAAO,cAAc,QAAQ,EAAE,kBAAkB,MAAM,CAAC;IACpE,GAAG,IAAI,MAAM,CAAC,CAAC,OAAO,YAAY;IAClC,oBAAoB,IAAI;GAC1B;EACF,CACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const require_home = require("./home.cjs");
|
|
2
|
+
const require_signin = require("./signin.cjs");
|
|
3
|
+
const require_publish = require("./publish.cjs");
|
|
4
|
+
const require_search = require("./search.cjs");
|
|
5
|
+
const require_settings = require("./settings.cjs");
|
|
6
|
+
const require_layout = require("./layout.cjs");
|
|
7
|
+
const require_change_password = require("./change-password.cjs");
|
|
8
|
+
const require_detail = require("./detail.cjs");
|
|
9
|
+
const require_failure_modes = require("./failure-modes.cjs");
|
|
10
|
+
const require_i18n_keys = require("./i18n-keys.cjs");
|
|
11
|
+
const require_signup = require("./signup.cjs");
|
|
12
|
+
const require_manifest_rendering = require("./manifest-rendering.cjs");
|
|
13
|
+
const require_session = require("./session.cjs");
|
|
14
|
+
exports.changePasswordTests = require_change_password.changePasswordTests;
|
|
15
|
+
exports.detailTests = require_detail.detailTests;
|
|
16
|
+
exports.failureModeTests = require_failure_modes.failureModeTests;
|
|
17
|
+
exports.homeTests = require_home.homeTests;
|
|
18
|
+
exports.i18nKeyTests = require_i18n_keys.i18nKeyTests;
|
|
19
|
+
exports.layoutTests = require_layout.layoutTests;
|
|
20
|
+
exports.manifestRenderingTests = require_manifest_rendering.manifestRenderingTests;
|
|
21
|
+
exports.publishTests = require_publish.publishTests;
|
|
22
|
+
exports.searchTests = require_search.searchTests;
|
|
23
|
+
exports.sessionTests = require_session.sessionTests;
|
|
24
|
+
exports.settingsTests = require_settings.settingsTests;
|
|
25
|
+
exports.signinTests = require_signin.signinTests;
|
|
26
|
+
exports.signupTests = require_signup.signupTests;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
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
|
+
require_features.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
|
+
require_features.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
|
+
require_features.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
|
+
exports.manifestRenderingTests = manifestRenderingTests;
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=manifest-rendering.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-rendering.cjs","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,iBAAA,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,iBAAA,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,iBAAA,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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
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
|
+
require_features.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
|
+
require_features.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
|
+
exports.sessionTests = sessionTests;
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=session.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.cjs","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,iBAAA,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,iBAAA,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"}
|
|
@@ -60,6 +60,34 @@ function settingsTests(config) {
|
|
|
60
60
|
cy.get("body").type("{esc}");
|
|
61
61
|
cy.get("[role=\"dialog\"]").should("not.exist");
|
|
62
62
|
});
|
|
63
|
+
require_features.maybeIt(features.settings.localizedDates)("relative dates must follow the selected language (dayjs locale)", () => {
|
|
64
|
+
const pkgName = "@verdaccio/dates-fixture";
|
|
65
|
+
let tempFolder = null;
|
|
66
|
+
cy.task("publishPackage", {
|
|
67
|
+
pkgName,
|
|
68
|
+
version: "1.0.0",
|
|
69
|
+
unique: true
|
|
70
|
+
}).then((result) => {
|
|
71
|
+
tempFolder = result?.tempFolder ?? null;
|
|
72
|
+
});
|
|
73
|
+
cy.getByTestId(header.settingsTooltip).click();
|
|
74
|
+
cy.get("[role=\"dialog\"]").should("be.visible");
|
|
75
|
+
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").click();
|
|
76
|
+
cy.contains("[role=\"dialog\"] .MuiCard-root", "German").click({ force: true });
|
|
77
|
+
cy.get("body").type("{esc}");
|
|
78
|
+
const sidebar = `[data-testid="${config.testIds.package.sidebar}"]`;
|
|
79
|
+
cy.visit(`${config.registryUrl}/-/web/detail/${pkgName}`);
|
|
80
|
+
cy.get(sidebar, { timeout: 1e4 }).should("be.visible");
|
|
81
|
+
cy.get(sidebar).contains(/vor /);
|
|
82
|
+
cy.get(sidebar).contains(/ago\b/).should("not.exist");
|
|
83
|
+
cy.task("unpublishPackage", {
|
|
84
|
+
pkgName,
|
|
85
|
+
tempFolder: tempFolder ?? void 0
|
|
86
|
+
});
|
|
87
|
+
cy.then(() => {
|
|
88
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
63
91
|
});
|
|
64
92
|
}
|
|
65
93
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.cjs","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').click({\n 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}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,cAAc,QAAwB;CACpD,MAAM,EAAE,WAAW,OAAO;CAC1B,MAAM,EAAE,aAAa;CAErB,SAAS,6BAA6B;EACpC,iBAAiB;GACf,GAAG,MAAM,OAAO,WAAW;GAC3B,GAAG,IAAI,MAAM,CAAC,CAAC,OAAO,YAAY;EACpC,CAAC;EAED,GAAG,yDAAyD;GAC1D,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,MAAM;GAC7C,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,YAAY;GAE7C,GAAG,SAAS,qBAAmB,eAAe,CAAC,CAAC,OAAO,YAAY;EACrE,CAAC;EAED,GAAG,iEAAiE;GAClE,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,MAAM;GAC7C,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,YAAY;GAC7C,GAAG,IAAI,kCAA8B,CAAC,CAAC,OAAO,eAAe,CAAC;GAC9D,GAAG,SAAS,oCAAgC,cAAc,CAAC,CAAC,OAAO,YAAY;EACjF,CAAC;EAED,GAAG,kEAAkE;GACnE,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,MAAM;GAC7C,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,YAAY;GAC7C,GAAG,SAAS,oCAAgC,cAAc,CAAC,CAAC,MAAM;GAElE,GAAG,SAAS,qBAAmB,qDAAqD,CAAC,CAAC,OACpF,YACF;GAEA,GAAG,SAAS,qBAAmB,SAAS,CAAC,CAAC,OAAO,YAAY;GAC7D,GAAG,SAAS,qBAAmB,QAAQ,CAAC,CAAC,OAAO,YAAY;EAC9D,CAAC;EAED,iBAAA,QAAQ,SAAS,SAAS,gBAAgB,CAAC,CACzC,uEACM;GACJ,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,MAAM;GAC7C,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,YAAY;GAC7C,GAAG,SAAS,oCAAgC,cAAc,CAAC,CAAC,MAAM;GAIlE,GAAG,SAAS,qBAAmB,eAAe,CAAC,CAC5C,OAAO,MAAM,CAAC,CACd,MAAM,iBAAiB;IAGtB,GAAG,SAAS,mCAAiC,QAAQ,CAAC,CAAC,MAAM,EAC3D,OAAO,KACT,CAAC;IAKD,GAAG,SAAS,oCAAgC,cAAc,CAAC,CAAC,OAAO,WAAW;IAE9E,GAAG,SAAS,qBAAmB,YAAY,CAAC,CAAC,OAAO,WAAW;GACjE,CAAC;EACL,CACF;EAEA,GAAG,sDAAsD;GACvD,GAAG,YAAY,OAAO,eAAe,CAAC,CAAC,MAAM;GAC7C,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,YAAY;GAC7C,GAAG,IAAI,MAAM,CAAC,CAAC,KAAK,OAAO;GAC3B,GAAG,IAAI,mBAAiB,CAAC,CAAC,OAAO,WAAW;EAC9C,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.cjs","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').click({\n 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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"}
|