Skip to content

Commit 1e4cddd

Browse files
integration: create charm using ci, view in jumble (#518)
* load a charm using cli, then view and use it in jumble * ci is slow? * close testing browser after integration tests * use unique space for each integration run * verify the updates propagate using CLI. * chore: smoke test follow ups --------- Co-authored-by: Jordan Santell <jordan@common.tools>
1 parent 3e31150 commit 1e4cddd

File tree

8 files changed

+206
-17
lines changed

8 files changed

+206
-17
lines changed

typescript/packages/common-charm/src/charm.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ export class CharmManager {
186186
const recipe = recipeId ? getRecipe(recipeId) : undefined;
187187

188188
if (!recipe || charm.get() === undefined) {
189+
console.warn("recipeId", recipeId);
190+
console.warn("recipe", recipe);
191+
console.warn("charm", charm.get());
189192
console.warn(`Not a charm: ${JSON.stringify(getEntityId(charm))}`);
190193
}
191194

typescript/packages/common-cli/main.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
import { getEntityId, isStream } from "@commontools/runner";
1010
import { Identity } from "@commontools/identity";
1111

12-
const { space, charmId, recipeFile, cause } = parseArgs(Deno.args, {
12+
const { space, charmId, recipeFile, cause, quit } = parseArgs(Deno.args, {
1313
string: ["space", "charmId", "recipeFile", "cause"],
14-
default: {},
14+
boolean: ["quit"],
15+
default: { quit: false },
1516
});
1617

1718
const toolshedUrl = Deno.env.get("TOOLSHED_API_URL") ??
@@ -22,22 +23,42 @@ setBobbyServerUrl(toolshedUrl);
2223

2324
async function main() {
2425
const identity = await Identity.fromPassphrase("common-cli");
25-
console.log("params:", { space, identity, charmId, recipeFile, cause });
26+
console.log("params:", {
27+
space,
28+
identity,
29+
charmId,
30+
recipeFile,
31+
cause,
32+
quit,
33+
toolshedUrl,
34+
});
2635
const manager = await CharmManager.open({
2736
space: (space as `did:key:${string}`) ?? identity.did(),
2837
signer: identity,
2938
});
30-
const charms = await manager.getCharms();
31-
39+
const charms = manager.getCharms();
3240
charms.sink((charms) => {
3341
console.log(
34-
"charms:",
35-
charms.map((c) => c.toJSON().cell?.["/"]),
42+
"all charms:",
43+
charms.map((c) => getEntityId(c)?.["/"]),
3644
);
3745
});
3846

3947
if (charmId) {
4048
const charm = await manager.get(charmId);
49+
if (quit) {
50+
if (!charm) {
51+
console.error("charm not found:", charmId);
52+
Deno.exit(1);
53+
}
54+
console.log("charm:", charmId);
55+
console.log("charm:", JSON.stringify(charm.get(), null, 2));
56+
console.log(
57+
"sourceCell:",
58+
JSON.stringify(charm.getSourceCell().get(), null, 2),
59+
);
60+
Deno.exit(0);
61+
}
4162
charm?.sink((value) => {
4263
console.log("charm:", charmId, value);
4364
});
@@ -59,8 +80,16 @@ async function main() {
5980
console.log("running updater");
6081
updater.send({ newValues: ["test"] });
6182
}
83+
if (quit) {
84+
await storage.synced();
85+
Deno.exit(0);
86+
}
6287
} catch (error) {
6388
console.error("Error loading and compiling recipe:", error);
89+
if (quit) {
90+
await storage.synced();
91+
Deno.exit(1);
92+
}
6493
}
6594
}
6695

typescript/packages/common-cli/recipes/simpleValue.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
recipe,
99
schema,
1010
UI,
11+
str,
1112
} from "@commontools/builder";
1213

1314
const updaterSchema = {
@@ -64,7 +65,7 @@ export default recipe(inputSchema, outputSchema, ({ values }) => {
6465
console.log("values#", values?.length);
6566
});
6667
return {
67-
[NAME]: "Simple Value",
68+
[NAME]: str`Simple Value: ${values.length}`,
6869
[UI]: (
6970
<div>
7071
<button type="button" onClick={adder({ values })}>Add Value</button>

typescript/packages/deno.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,82 @@
1-
import { launch } from "@astral/astral";
1+
import { Browser, launch } from "@astral/astral";
22
import { assert } from "@std/assert";
3-
import { login } from "./utils.ts";
3+
import {
4+
addCharm,
5+
inspectCharm,
6+
login,
7+
sleep,
8+
waitForSelectorWithText,
9+
} from "./utils.ts";
410

5-
const FRONTEND_URL = "http://localhost:5173/";
11+
const TOOLSHED_API_URL = Deno.env.get("TOOLSHED_API_URL") ??
12+
"http://localhost:8000/";
13+
const FRONTEND_URL = Deno.env.get("FRONTEND_URL") ?? "http://localhost:5173/";
14+
15+
async function main(browser: Browser) {
16+
console.log(`TOOLSHED_API_URL=${TOOLSHED_API_URL}`);
17+
console.log(`FRONTEND_URL=${FRONTEND_URL}`);
18+
19+
const { charmId, space } = await addCharm(TOOLSHED_API_URL);
20+
console.log(`Charm added`, { charmId, space });
621

7-
async function main() {
8-
const browser = await launch();
922
console.log(`Waiting to open website at ${FRONTEND_URL}`);
1023
const page = await browser.newPage(FRONTEND_URL);
1124
console.log(`Opened website at ${FRONTEND_URL}`);
1225

26+
console.log("Logging in");
1327
await login(page);
1428

29+
console.log("Checking if logged in");
1530
const anchor = await page.waitForSelector("nav a");
1631
assert(
1732
(await anchor.innerText()) === "common-knowledge",
1833
"Logged in and Common Knowledge title renders",
1934
);
20-
await browser.close();
35+
36+
await page.goto(`${FRONTEND_URL}${space}/${charmId}`);
37+
console.log(`Waiting for charm to render`);
38+
39+
await waitForSelectorWithText(
40+
page,
41+
"a[aria-current='charm-title']",
42+
"Simple Value: 1",
43+
);
44+
console.log("Charm rendered.");
45+
46+
console.log("Clicking button");
47+
// Sometimes clicking this button throws:
48+
// https://jsr.io/@astral/astral/0.5.2/src/element_handle.ts#L192
49+
// As if the reference was invalidated by a spurious re-render between
50+
// getting an element handle, and clicking it.
51+
await sleep(1000);
52+
const button = await page.waitForSelector(
53+
"div[aria-label='charm-content'] button",
54+
);
55+
await button.click();
56+
57+
console.log("Checking if title changed");
58+
await waitForSelectorWithText(
59+
page,
60+
"a[aria-current='charm-title']",
61+
"Simple Value: 2",
62+
);
63+
console.log("Title changed");
64+
65+
console.log("Inspecting charm to verify updates propagated from browser.");
66+
const charm = await inspectCharm(TOOLSHED_API_URL, space, charmId);
67+
console.log("Charm:", charm);
68+
assert(charm.includes("Simple Value: 2"), "Charm updates propagated.");
2169
}
2270

71+
let browser = null;
2372
try {
24-
await main();
73+
browser = await launch();
74+
await main(browser);
75+
await browser.close();
2576
} catch (e) {
77+
if (browser) {
78+
await browser.close();
79+
}
2680
console.error(e);
2781
Deno.exit(1);
2882
}

typescript/packages/jumble/integration/utils.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import { Page } from "@astral/astral";
1+
import { ElementHandle, Page } from "@astral/astral";
2+
import * as path from "@std/path";
3+
4+
const COMMON_CLI_PATH = path.join(import.meta.dirname!, "../../common-cli");
5+
6+
export const decode = (() => {
7+
const decoder = new TextDecoder();
8+
return (buffer: Uint8Array): string => decoder.decode(buffer);
9+
})();
10+
11+
export const sleep = (ms: number) =>
12+
new Promise((resolve) => setTimeout(resolve, ms));
213

314
export const login = async (page: Page) => {
415
// First, see if any credential data is
@@ -39,3 +50,87 @@ export const login = async (page: Page) => {
3950
button = await page.$("button");
4051
await button!.click();
4152
};
53+
54+
export const waitForSelectorWithText = async (
55+
page: Page,
56+
selector: string,
57+
text: string,
58+
): Promise<ElementHandle> => {
59+
const retries = 30;
60+
const timeout = 200;
61+
62+
for (let i = 0; i < retries; i++) {
63+
const el = await page.$(selector);
64+
if (!el) {
65+
await sleep(timeout);
66+
continue;
67+
}
68+
if ((await el.innerText()) === text) {
69+
return el;
70+
}
71+
}
72+
throw new Error(`Timed out waiting for "${selector}" to have text "${text}"`);
73+
};
74+
75+
export const addCharm = async (toolshedUrl: string) => {
76+
const space = `ci-${Date.now()}-${
77+
Math.random().toString(36).substring(2, 15)
78+
}`;
79+
const { success, stderr } = await (new Deno.Command(Deno.execPath(), {
80+
args: [
81+
"task",
82+
"start",
83+
"--space",
84+
space,
85+
"--recipeFile",
86+
"recipes/simpleValue.tsx",
87+
"--cause",
88+
"ci",
89+
"--quit",
90+
"true",
91+
],
92+
env: {
93+
"TOOLSHED_API_URL": toolshedUrl,
94+
},
95+
cwd: COMMON_CLI_PATH,
96+
})).output();
97+
98+
if (!success) {
99+
throw new Error(`Failed to add charm: ${decode(stderr)}`);
100+
}
101+
102+
return {
103+
charmId: "baedreic5a2muxtlgvn6u36lmcp3tdoq5sih3nbachysw4srquvga5fjtem",
104+
space,
105+
};
106+
};
107+
108+
export const inspectCharm = async (
109+
toolshedUrl: string,
110+
space: string,
111+
charmId: string,
112+
) => {
113+
const { success, stdout, stderr } = await (new Deno.Command(Deno.execPath(), {
114+
args: [
115+
"task",
116+
"start",
117+
"--space",
118+
space,
119+
"--charmId",
120+
charmId,
121+
"--quit",
122+
"true",
123+
],
124+
env: {
125+
"TOOLSHED_API_URL": toolshedUrl,
126+
},
127+
cwd: COMMON_CLI_PATH,
128+
})).output();
129+
130+
if (!success) {
131+
console.log(decode(stdout));
132+
throw new Error(`Failed to inspect charm: ${decode(stderr)}`);
133+
}
134+
135+
return decode(stdout);
136+
};

typescript/packages/jumble/src/components/CharmRunner.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,12 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) {
168168
</div>
169169
)
170170
: null}
171-
<div className={className + " overflow-clip"} ref={containerRef}></div>
171+
<div
172+
className={className + " overflow-clip"}
173+
ref={containerRef}
174+
aria-label="charm-content"
175+
>
176+
</div>
172177
</>
173178
);
174179
}

typescript/packages/jumble/src/components/NavPath.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function NavPath({ replicaId, charmId }: NavPathProps) {
5454
<NavLink
5555
to={`/${replicaId}/${charmId}`}
5656
className="text-gray-700 font-bold"
57+
aria-current="charm-title"
5758
>
5859
{charmName}
5960
</NavLink>

0 commit comments

Comments
 (0)