Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180import { expect } from "@playwright/test";
import { getOk } from "../harness/api";import { config } from "../harness/config";import { bobbin, type Ctx, flow, hydrant, link, not, pds, record, replacing, rkeyOf, starId, target, text, testid, through, where, within} from "../harness/flow";import { get, until, xrpc } from "../harness/net";import { acceptConfirm, pdsRecordUrl, recall, remember } from "../harness/shared";
const string = record("org.tangled.snippet.string");const stringFilename = "notes.txt";const stringBody = "the needle is in the haystack";const newString = where("the new string form", () => "/strings/new");const stringPage = where( "the string page", (ctx) => `/strings/${recall(ctx, "writer").handle}/${rkeyOf(ctx, string)}`);const stringsTab = where( "the writer's strings tab", (ctx) => `/${recall(ctx, "writer").handle}?tab=strings`);// the editor is a web component, its contenteditable is in a shadow rootconst stringEditor = target("the string editor", (tab) => tab.locator("diffs-container").getByRole("textbox"));const typeInEditor = (words: string, replace = false) => async (ctx: Ctx) => { const editor = stringEditor.locate(ctx.page, ctx); const typed = ctx.page.locator( `[data-testid="contentBytes"][data-bytes="${Buffer.byteLength(words)}"]` ); // the editor drops keys when it redraws after the filename changes await until("the editor should hold what was typed", 30_000, async () => { await editor.click(); await ctx.page.waitForTimeout(500); await ctx.page.keyboard.press("ControlOrMeta+a"); if (!replace) await ctx.page.keyboard.press("Backspace"); await editor.pressSequentially(words, { delay: 20 }); const held = await typed.isVisible({ timeout: 1500 }).catch(() => false); return [held, held ? "it does" : "it does not"]; }); };
const starredStringCard = target("the starred string's card", (tab, ctx) => tab.locator( `[data-component="StringCardContent"]:has(a[href="/strings/${recall(ctx, "writer").handle}/${rkeyOf(ctx, string)}"])` ));
const stringOnPds = async (ctx: Ctx) => { const { value } = await getOk( pdsRecordUrl(recall(ctx, "writer").did, string.nsid, rkeyOf(ctx, string)) ); const blob = await get( xrpc(config.pdsUrl, "com.atproto.sync.getBlob", { did: recall(ctx, "writer").did, cid: value.snippet.content.ref.$link }) ); return { value, contents: blob.body as string };};
const writtenString = (name: string) => flow(name) .actor("writer") .step("remember the writer", remember("writer")) .open(newString) .fill(testid("filename"), stringFilename) .fill(testid("description"), "my notes") .step("types the content", typeInEditor(stringBody)) .click(testid("submit")) .sends( string, pds, { record: { $type: string.nsid, snippet: { name: stringFilename }, description: "my notes" } }, "put" ) .trace(string, through(pds, hydrant, bobbin)) .at(stringPage) .sees(text(stringBody));
writtenString("creating a string") .step("the pds keeps the text in a blob", async (ctx) => { const { value, contents } = await stringOnPds(ctx); expect(value.snippet.name).toBe(stringFilename); expect(contents).toBe(stringBody); }) .reload() .sees(text(stringBody)) .sees(within(testid("stringDescription"), text("my notes"))) .open(stringsTab) .sees(link(stringFilename));
const edited = "the needle moved to another haystack";
writtenString("editing a string") .click(testid("edit")) .fill(testid("description"), "my edited notes") .step("types the new content", typeInEditor(edited, true)) .click(testid("submit")) .sends( string, pds, replacing(string, { snippet: { name: stringFilename }, description: "my edited notes" }), "put" ) .trace(string, through(pds, hydrant, bobbin)) .sees(text(edited)) .sees(within(testid("stringDescription"), text("my edited notes"))) .step("the pds keeps the new text", async (ctx) => { expect((await stringOnPds(ctx)).contents).toBe(edited); }) .reload() .sees(text(edited)) .sees(not(text(stringBody)));
writtenString("deleting a string") .step("agree to the confirm", acceptConfirm) .click(testid("moreMenu")) .click(testid("delete")) .sends(string, pds, replacing(string), "delete") .trace(string, through(pds, hydrant, bobbin)) .at(stringsTab) .sees(not(link(stringFilename))) .open(stringPage) .sees(testid("errorTitle", { status: "404" }));
writtenString("starring a string") .actor("fan") .open(stringPage) .click(testid("star", { starred: "false" })) .sends( starId, pds, (ctx) => ({ record: { subject: { $type: "org.tangled.feed.star#record", record: { uri: ctx.get(string).uri, cid: ctx.get(string).cid } } } }), "create" ) .sees(testid("star", { starred: "true" })) .trace(starId, through(pds, hydrant, bobbin)) .reload() .sees(testid("star", { starred: "true" })) .open(where("the fan's starred tab", (ctx) => `/${ctx.actor.handle}?tab=starred`)) .sees(starredStringCard) .sees(within(starredStringCard, link(stringFilename))) .sees(within(starredStringCard, text("my notes")));