mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 08:17:26 +01:00
merge: reconcile paid hosting with latest main
This commit is contained in:
commit
ded6f905fd
180 files changed
+21709
-3614
No files matched your search
+359
-1
@@ -17,7 +17,8 @@ async function browser(route = "/", overrides = {}, storage = {}) {
|
||||
errors.push(error.message);
|
||||
});
|
||||
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
|
||||
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
|
||||
const base = fs.readFileSync(path.join(publicDir, "index.html"), "utf8").match(/<base\b[^>]*>/i)?.[0] || "";
|
||||
const dom = new JSDOM(`<!doctype html><html><head>${base}</head><body><div id="app"></div></body></html>`, {
|
||||
resources: {
|
||||
interceptors: [requestInterceptor(request => {
|
||||
const url = request.url;
|
||||
@@ -200,6 +201,150 @@ describe("Vue 3 UI", function () {
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("opens account settings without leaving the profile page", async () => {
|
||||
ui = await browser("/profile", { "/api/user": { username: "owner" } });
|
||||
const account = [...ui.window.document.querySelectorAll('.settings-nav button')].find(button => button.textContent.trim() === "Account");
|
||||
account.click();
|
||||
await delay(30);
|
||||
expect(ui.window.location.pathname).to.equal("/profile");
|
||||
expect(ui.window.document.querySelector("#settings-account").style.display).not.to.equal("none");
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("loads and saves account email separately from defaults with CSRF", async () => {
|
||||
ui = await browser("/profile", {
|
||||
"/api/user": { username: "owner" },
|
||||
"/github/connections/email": request => request.method === "POST"
|
||||
? { notificationEmail: request.payload.email.trim() }
|
||||
: { notificationEmail: "old@example.com", csrf: "profile-csrf" },
|
||||
});
|
||||
expect(ui.window.document.querySelector("#account-email").value).to.equal("old@example.com");
|
||||
await ui.input("#account-email", "new@example.com");
|
||||
ui.window.document.querySelector('.paper-account-email button[type="submit"]').click();
|
||||
await delay(30);
|
||||
const save = ui.requests.find(r => r.url.pathname === "/github/connections/email" && r.method === "POST");
|
||||
expect(save.payload).to.deep.equal({ email: "new@example.com" });
|
||||
expect(save.headers["X-CSRF-Token"]).to.equal("profile-csrf");
|
||||
expect(ui.requests.filter(r => r.url.pathname === "/api/user/default" && r.method === "POST")).to.have.length(0);
|
||||
expect(ui.window.document.querySelector(".paper-account-email").textContent).to.include("Email address saved.");
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("retries email loading and keeps failed edits while preventing duplicate saves", async () => {
|
||||
let failLoad = true, finishSave;
|
||||
ui = await browser("/profile", {
|
||||
"/api/user": { username: "owner" },
|
||||
"/github/connections/email": request => request.method === "POST"
|
||||
? new Promise(resolve => { finishSave = () => resolve({ __status: 503, body: {} }); })
|
||||
: failLoad ? { __status: 503, body: {} } : { notificationEmail: "", csrf: "profile-csrf" },
|
||||
});
|
||||
expect(ui.window.document.querySelector("#account-email").disabled).to.equal(true);
|
||||
failLoad = false;
|
||||
ui.window.document.querySelector('.paper-account-email button[type="button"]').click();
|
||||
await delay(30);
|
||||
await ui.input("#account-email", "owner+alerts@example.com");
|
||||
const form = ui.window.document.querySelector(".paper-account-email");
|
||||
form.dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
|
||||
form.dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
|
||||
await delay(10);
|
||||
expect(ui.requests.filter(r => r.url.pathname === "/github/connections/email" && r.method === "POST")).to.have.length(1);
|
||||
expect(ui.window.document.querySelector("#account-email").disabled).to.equal(true);
|
||||
finishSave(); await delay(30);
|
||||
expect(ui.window.document.querySelector("#account-email").value).to.equal("owner+alerts@example.com");
|
||||
expect(form.querySelector('[role="alert"]').textContent).to.include("Unable to save");
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("prompts owners without email and saves their alert address with CSRF", async () => {
|
||||
ui = await browser("/dashboard", {
|
||||
"/api/user": { username: "owner", notificationEmail: "", emailNotificationsEnabled: true, showEmailPrompt: true, emailPromptCSRF: "email-csrf" },
|
||||
"/github/connections": { csrf: "email-csrf", emailNotificationsEnabled: true, notificationEmail: "", resources: [] },
|
||||
"/github/connections/email": request => ({ notificationEmail: request.payload.email }),
|
||||
});
|
||||
expect(ui.window.document.querySelector("#email-prompt").open).to.equal(true);
|
||||
await ui.input("#email-prompt-address", "owner@example.com");
|
||||
ui.window.document.querySelector("#email-prompt-address").closest("form")
|
||||
.dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
|
||||
await delay(30);
|
||||
const saved = ui.requests.find(r => r.url.pathname === "/github/connections/email");
|
||||
expect(saved.payload).to.deep.equal({ action: "save", email: "owner@example.com" });
|
||||
expect(saved.headers["X-CSRF-Token"]).to.equal("email-csrf");
|
||||
expect(ui.window.document.querySelector("#email-prompt")).to.equal(null);
|
||||
await ui.go("/dashboard");
|
||||
expect(ui.window.document.querySelector("#email-prompt")).to.equal(null);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
for (const action of ["later", "never"]) it(`dismisses the email popup with ${action} without requiring an address`, async () => {
|
||||
ui = await browser("/dashboard", {
|
||||
"/api/user": { username: "owner", notificationEmail: "", emailNotificationsEnabled: true, showEmailPrompt: true, emailPromptCSRF: "csrf" },
|
||||
"/github/connections/email": { ok: true },
|
||||
});
|
||||
const text = action === "later" ? "Remind me later" : "Never";
|
||||
[...ui.window.document.querySelectorAll("#email-prompt button")].find(b => b.textContent === text).click();
|
||||
await delay(30);
|
||||
expect(ui.requests.find(r => r.url.pathname === "/github/connections/email").payload.action).to.equal(action);
|
||||
expect(ui.window.document.querySelector("#email-prompt")).to.equal(null);
|
||||
});
|
||||
|
||||
for (const user of [
|
||||
{ emailNotificationsEnabled: false, showEmailPrompt: true },
|
||||
{ emailNotificationsEnabled: true, showEmailPrompt: false },
|
||||
{ emailNotificationsEnabled: true, showEmailPrompt: true, notificationEmail: "owner@example.com" },
|
||||
]) it(`does not show the email popup for ${JSON.stringify(user)}`, async () => {
|
||||
ui = await browser("/dashboard", { "/api/user": { username: "owner", ...user } });
|
||||
expect(ui.window.document.querySelector("#email-prompt")).to.equal(null);
|
||||
});
|
||||
|
||||
it("keeps the popup open and shows an error if saving fails", async () => {
|
||||
ui = await browser("/dashboard", {
|
||||
"/api/user": { username: "owner", emailNotificationsEnabled: true, showEmailPrompt: true, emailPromptCSRF: "csrf" },
|
||||
"/github/connections/email": { __status: 500, body: { error: "Unable to save." } },
|
||||
});
|
||||
await ui.input("#email-prompt-address", "owner@example.com");
|
||||
ui.window.document.querySelector("#email-prompt form").dispatchEvent(new ui.window.Event("submit", { bubbles: true, cancelable: true }));
|
||||
await delay(30);
|
||||
expect(ui.window.document.querySelector("#email-prompt").textContent).to.include("Unable to save.");
|
||||
expect(ui.window.document.querySelector("#email-prompt-address").value).to.equal("owner@example.com");
|
||||
});
|
||||
|
||||
for (const code of ["repo_not_found", "token_expired", "github_app_reconnect_required"]) {
|
||||
it(`explains ${code} without exposing the source and retries after access recovers`, async () => {
|
||||
let recovered = false;
|
||||
ui = await browser("/r/access-test/hello.js", {
|
||||
"/api/repo/access-test/file/hello.js": () => recovered ? "recovered content" : {
|
||||
__status: 404, body: { error: code, detail: "private-owner/private-source", token: "secret" },
|
||||
},
|
||||
});
|
||||
await delay(100);
|
||||
const panel = ui.window.document.querySelector(".viewer-error");
|
||||
expect(panel?.textContent).to.include("GitHub access needs attention");
|
||||
expect(panel.textContent).to.include("You do not need to sign in");
|
||||
expect(panel.textContent).to.include("Keep the existing anonymous URL");
|
||||
expect(panel.textContent).not.to.include("private-owner");
|
||||
expect(panel.textContent).not.to.include("secret");
|
||||
recovered = true;
|
||||
panel.querySelector("button").click();
|
||||
await delay(150);
|
||||
expect(ui.window.document.querySelector(".viewer-error")).to.equal(null);
|
||||
expect(ui.requests.filter(request => request.url.pathname.endsWith("/file/hello.js"))).to.have.length(2);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
}
|
||||
|
||||
it("offers a safe retry for an unrecognized gateway response", async () => {
|
||||
ui = await browser("/r/access-test/hello.js", {
|
||||
"/api/repo/access-test/options": { __status: 403, body: "<html>private diagnostic</html>" },
|
||||
});
|
||||
await delay(100);
|
||||
const panel = ui.window.document.querySelector(".viewer-error");
|
||||
expect(panel?.textContent).to.include("We could not load this content");
|
||||
expect(panel.textContent).not.to.include("private diagnostic");
|
||||
expect(panel.textContent).not.to.include("GitHub access needs attention");
|
||||
expect(panel.querySelector("button").textContent).to.equal("Try again");
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
const repositorySource = {
|
||||
"/api/user": { username: "owner" },
|
||||
"/api/user/default": { terms: ["Default Author"], options: {} },
|
||||
@@ -340,6 +485,95 @@ describe("Vue 3 UI", function () {
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("suggests opening existing configurations for the same repository URL without copying settings", async () => {
|
||||
ui = await browser("/anonymize", {
|
||||
...repositorySource,
|
||||
"/api/user/anonymized_repositories": [
|
||||
{ repoId: "older", source: { fullName: "Owner/Repo" }, options: { terms: ["Old Author"] }, anonymizeDate: "2025-01-01" },
|
||||
{ repoId: "newer", source: { fullName: "owner/repo" }, options: { terms: ["New Author"] }, anonymizeDate: "2026-01-01" },
|
||||
{ repoId: "unrelated", source: { fullName: "owner/other" }, options: {} },
|
||||
],
|
||||
});
|
||||
const state = ui.window.document.querySelector("#sourceUrl")._field.binding.state;
|
||||
state.sourceUrl = "https://github.com/owner/repo.git/";
|
||||
await state.urlSelected();
|
||||
await delay(30);
|
||||
const suggestion = ui.window.document.querySelector('[aria-label="Previous repository configuration"]');
|
||||
expect(suggestion.textContent).to.include("avoid creating another repository");
|
||||
expect([...suggestion.querySelectorAll("option")].map(option => option.value)).to.deep.equal(["newer", "older"]);
|
||||
expect(suggestion.querySelector("a").getAttribute("href")).to.equal("/anonymize/newer");
|
||||
await ui.input("#previousConfiguration", "older", "change");
|
||||
expect(suggestion.querySelector("a").getAttribute("href")).to.equal("/anonymize/older");
|
||||
expect(state.terms).to.equal("Default Author");
|
||||
expect(state.isUpdate).to.equal(false);
|
||||
suggestion.querySelector("button").click();
|
||||
await delay(10);
|
||||
expect(ui.window.document.querySelector('[aria-label="Previous repository configuration"]')).to.equal(null);
|
||||
expect(ui.requests.filter(request => request.method === "POST" && request.url.pathname === "/api/repo/")).to.have.length(0);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("opens the existing configuration from the suggestion without creating a repository", async () => {
|
||||
const existing = { repoId: "existing-review", status: "ready", role: "owner",
|
||||
source: { fullName: "owner/repo", branch: "main", commit: "abcdef123" },
|
||||
options: { terms: ["Existing Author"], expirationMode: "never" } };
|
||||
ui = await browser("/anonymize", {
|
||||
...repositorySource,
|
||||
"/api/user/anonymized_repositories": [existing],
|
||||
"/api/repo/existing-review": existing,
|
||||
});
|
||||
const sourceInput = await ui.input("#sourceUrl-landing", "https://github.com/owner/repo");
|
||||
sourceInput.dispatchEvent(new ui.window.Event("blur"));
|
||||
await delay(250);
|
||||
const link = ui.window.document.querySelector('[aria-label="Previous repository configuration"] a');
|
||||
expect(link).not.to.equal(null);
|
||||
link.click();
|
||||
await delay(250);
|
||||
const state = ui.window.document.querySelector("#sourceUrl")._field.binding.state;
|
||||
expect(ui.window.location.pathname).to.equal("/anonymize/existing-review");
|
||||
expect(state.isUpdate).to.equal(true);
|
||||
expect(state.repoId).to.equal("existing-review");
|
||||
expect(state.terms).to.equal("Existing Author");
|
||||
expect(state.source.commit).to.equal("abcdef123");
|
||||
expect(ui.window.document.querySelector('[aria-label="Previous repository configuration"]')).to.equal(null);
|
||||
expect(ui.requests.filter(request => request.method === "POST" && request.url.pathname.startsWith("/api/repo"))).to.have.length(0);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("ignores delayed duplicate suggestions after switching to an invalid URL", async () => {
|
||||
let finishLookup;
|
||||
ui = await browser("/anonymize", {
|
||||
...repositorySource,
|
||||
"/api/user/anonymized_repositories": () => new Promise(resolve => {
|
||||
finishLookup = () => resolve([{ repoId: "existing", source: { fullName: "owner/repo" }, options: {} }]);
|
||||
}),
|
||||
});
|
||||
const state = ui.window.document.querySelector("#sourceUrl")._field.binding.state;
|
||||
state.sourceUrl = "https://github.com/owner/repo";
|
||||
await state.urlSelected();
|
||||
state.sourceUrl = "invalid";
|
||||
await state.urlSelected();
|
||||
finishLookup();
|
||||
await delay(30);
|
||||
expect(state.previousConfigurations).to.have.length(0);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("allows creation when duplicate lookup fails", async () => {
|
||||
ui = await browser("/anonymize", {
|
||||
...repositorySource,
|
||||
"/api/user/anonymized_repositories": { __status: 503, body: { error: "unavailable" } },
|
||||
});
|
||||
const state = ui.window.document.querySelector("#sourceUrl")._field.binding.state;
|
||||
state.sourceUrl = "https://github.com/owner/repo";
|
||||
await state.urlSelected();
|
||||
await delay(30);
|
||||
expect(state.previousConfigurations).to.have.length(0);
|
||||
expect(state.details.repo).to.equal("repo");
|
||||
expect(state.sourceLoading).to.equal(false);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("keeps the current repository when earlier metadata and README responses arrive late", async () => {
|
||||
let finishMetadata, finishReadme;
|
||||
ui = await browser("/anonymize", {
|
||||
@@ -934,6 +1168,30 @@ describe("Vue 3 UI", function () {
|
||||
});
|
||||
}
|
||||
|
||||
for (const type of ["repo", "pr"]) {
|
||||
it(`offers installation access to App-only users for private ${type} sources`, async function () {
|
||||
const sourceUrl = "https://github.com/owner/private" + (type === "pr" ? "/pull/7" : "");
|
||||
ui = await browser("/anonymize", {
|
||||
"/api/user": { username: "owner" },
|
||||
"/github/connections": { appEnabled: true, appConnected: true, oauthConnected: false },
|
||||
"/api/repo/owner/private/": { __status: 403, body: { error: "github_app_access_required" } },
|
||||
"/api/pr/owner/private/7": { __status: 403, body: { error: "github_app_access_required" } },
|
||||
});
|
||||
const input = await ui.input("#sourceUrl-landing", sourceUrl);
|
||||
input.dispatchEvent(new ui.window.Event("blur"));
|
||||
await delay(60);
|
||||
const workspace = ui.window.document.querySelector(".anonymize-workspace");
|
||||
const button = [...workspace.querySelectorAll("button")].find(b => b.textContent.trim() === "Manage access");
|
||||
expect(button).not.to.equal(undefined);
|
||||
for (let node = button; node; node = node.parentElement) expect(ui.window.getComputedStyle(node).display).not.to.equal("none");
|
||||
expect(workspace.textContent).to.include("Signing in does not grant access to private repositories");
|
||||
button.click();
|
||||
const draft = JSON.parse(ui.window.sessionStorage.getItem("github-access-draft"));
|
||||
expect(draft.draft.sourceUrl).to.equal(sourceUrl);
|
||||
expect(ui.requests.some(r => r.method === "POST")).to.equal(false);
|
||||
});
|
||||
}
|
||||
|
||||
it("selects the connected GitHub App by default beside the source URL", async () => {
|
||||
ui = await browser("/anonymize", { "/github/connections": { appEnabled: true, appConnected: true, oauthConnected: true } });
|
||||
const panel = ui.window.document.querySelector(".anonymize-landing-inner .repo-access");
|
||||
@@ -1044,6 +1302,73 @@ describe("Vue 3 UI", function () {
|
||||
});
|
||||
}
|
||||
|
||||
for (const scenario of ["success", "failure", "renamed"]) {
|
||||
it(`repairs App access without replacing the source or snapshot (${scenario})`, async function () {
|
||||
const failed = scenario === "failure";
|
||||
const fullName = scenario === "renamed" ? "owner/renamed" : "owner/repo";
|
||||
let repaired = false;
|
||||
const available = value => () => repaired ? value : { __status: 403, body: { error: "github_app_access_required" } };
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/api/user": { username: "owner" },
|
||||
"/github/connections": { csrf: "repair-csrf", appEnabled: true, appConnected: true },
|
||||
"/api/repo/test": { connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", repositoryID: "saved-record", branch: "main", commit: "saved-sha" },
|
||||
options: { terms: ["author"], update: false, expirationMode: "never" } },
|
||||
"/api/repo/owner/repo/": available({ id: "saved-record", repo: "repo", defaultBranch: "main" }),
|
||||
"/api/repo/owner/repo/branches": available([{ name: "main", commit: "new-sha" }]),
|
||||
"/api/repo/owner/repo/readme": available("readme"),
|
||||
"/github/connections/migrate": async request => {
|
||||
expect(request.headers["X-CSRF-Token"]).to.equal("repair-csrf");
|
||||
expect(request.payload).to.deep.equal({ type: "repository", id: "test", connection: "github-app" });
|
||||
await delay(20);
|
||||
if (failed) return { __status: 403, body: { error: "github_app_access_required" } };
|
||||
repaired = true;
|
||||
return { connection: "github-app", fullName };
|
||||
},
|
||||
"/api/repo/owner/renamed/": { id: "saved-record", repo: "renamed", defaultBranch: "main" },
|
||||
"/api/repo/owner/renamed/branches": [{ name: "main", commit: "new-sha" }],
|
||||
"/api/repo/owner/renamed/readme": "readme",
|
||||
});
|
||||
await delay(60);
|
||||
const button = [...ui.window.document.querySelectorAll("button")].find(b => b.textContent === "Repair GitHub access");
|
||||
expect(button).not.to.equal(undefined);
|
||||
for (let node = button; node; node = node.parentElement) expect(ui.window.getComputedStyle(node).display).not.to.equal("none");
|
||||
button.click(); button.click();
|
||||
await delay(100);
|
||||
expect(ui.requests.filter(r => r.method === "POST")).to.have.length(1);
|
||||
expect(ui.window.document.querySelector("#commit").value).to.equal("saved-sha");
|
||||
expect(ui.requests.some(r => r.url.searchParams.get("reconnect") === "1")).to.equal(false);
|
||||
expect(ui.window.document.body.textContent).to.include(failed ? "Unable to repair GitHub access" : "GitHub access repaired");
|
||||
if (scenario === "renamed") {
|
||||
expect(ui.window.document.querySelector("#sourceUrl").value).to.equal("https://github.com/owner/renamed");
|
||||
const branches = ui.requests.find(r => r.url.pathname === "/api/repo/owner/renamed/branches");
|
||||
expect(branches.url.searchParams.get("anonymizedRepoId")).to.equal("test");
|
||||
expect(ui.window.document.querySelector("#branch").value).to.equal("main");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
it("hides App repair controls when migrations are disabled", async function () {
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/github/connections": { appEnabled: false, appConnected: true },
|
||||
"/api/repo/test": { connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
|
||||
});
|
||||
expect([...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access" || b.textContent === "Manage access")).to.equal(false);
|
||||
});
|
||||
|
||||
for (const status of ["removed", "archived", "preparing", "queue", "download", "removing", "expiring", "ready", "error", "expired"]) {
|
||||
it(`gates App access repair for repository status ${status}`, async function () {
|
||||
ui = await browser("/anonymize/test", {
|
||||
"/github/connections": { appEnabled: true, appConnected: true },
|
||||
"/api/repo/test": { status, connection: "github-app", role: "owner",
|
||||
source: { fullName: "owner/repo", branch: "main", commit: "saved-sha" }, options: { terms: [], update: false } },
|
||||
});
|
||||
const shown = [...ui.window.document.querySelectorAll("button")].some(b => b.textContent === "Repair GitHub access");
|
||||
expect(shown).to.equal(["ready", "error", "expired"].includes(status));
|
||||
});
|
||||
}
|
||||
|
||||
it("reconnects a recreated source without saving until the owner submits", async function () {
|
||||
const oldCommit = "deadbeef";
|
||||
const newCommit = "1a212f1deb74b123804ec90d098c0f843c30da5b";
|
||||
@@ -1190,6 +1515,39 @@ describe("Vue 3 UI", function () {
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("keeps the reviewer header to the essentials and shows when the link expires", async function () {
|
||||
ui = await browser("/r/test/hello.txt", {
|
||||
"/api/repo/test/options": { anonymizedAt: "2026-01-01T00:00:00Z", expirationDate: "2099-01-01T00:00:00Z" },
|
||||
});
|
||||
const header = ui.window.document.querySelector(".navbar");
|
||||
expect(header.querySelector(".btn-signin")).to.equal(null);
|
||||
expect(header.textContent).to.include("Report a bug");
|
||||
expect(ui.window.document.querySelector(".project-expiry").textContent).to.include("Available until");
|
||||
await ui.go("/faq");
|
||||
expect(ui.window.document.querySelector(".navbar .btn-signin")).to.not.equal(null);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it("leads a ready status page with the copyable anonymous link", async function () {
|
||||
ui = await browser("/status/test", { "/api/repo/test": { repoId: "test", status: "ready", options: {} } });
|
||||
const input = ui.window.document.querySelector(".share-link-input");
|
||||
expect(input.value).to.match(/\/r\/test\/$/);
|
||||
expect(ui.window.document.querySelector(".paper-progress")).to.equal(null);
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
|
||||
for (const [code, message] of [["repo_not_found", "GitHub access needs attention"], ["untranslated_failure", "We could not load this content"]]) {
|
||||
it("shows the " + code + " explorer error in a card without file actions", async function () {
|
||||
ui = await browser("/r/gone/", { "/api/repo/gone/options": { __status: 404, body: { error: code } } });
|
||||
const card = ui.window.document.querySelector(".file-error-card");
|
||||
expect(card.textContent).to.include(message);
|
||||
expect(card.textContent).not.to.include("ERRORS.");
|
||||
expect(card.querySelector(code === "repo_not_found" ? 'a[href="/dashboard"]' : 'a[href="/"]')).to.not.equal(null);
|
||||
expect(ui.window.document.querySelector(".status-bar").style.display).to.equal("none");
|
||||
expect(ui.errors).to.deep.equal([]);
|
||||
});
|
||||
}
|
||||
|
||||
it("renders hostile filenames as text and updates the explorer without losing the tree", async function () {
|
||||
const filename = '{{constructor.constructor("window.probe=1")()}}.txt';
|
||||
ui = await browser("/r/test/hello.txt", {
|
||||
|
||||
Reference in new issue
Block a user