Skip to content

Commit 8a693f5

Browse files
ebebbingtoncrookse
andauthored
feat: Support dialogs (#124)
* feat: Support dialogs * rm logging * fix(page): add period to end of error message * Update server.ts * Update server.ts * Update page_test.ts Co-authored-by: Eric Crooks <[email protected]>
1 parent d33cc70 commit 8a693f5

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

src/page.ts

+61
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,67 @@ export class Page {
4646
return this.#protocol.socket;
4747
}
4848

49+
/**
50+
* Tells Sinco you are expecting a dialog, so Sinco can listen for the event,
51+
* and when `.dialog()` is called, Sinco can accept or decline it at the right time
52+
*
53+
* @example
54+
* ```js
55+
* // Note that if `.click()` produces a dialog, do not await it.
56+
* await page.expectDialog();
57+
* await elem.click();
58+
* await page.dialog(true, "my username is Sinco");
59+
* ```
60+
*/
61+
public expectDialog() {
62+
this.#protocol.notifications.set(
63+
"Page.javascriptDialogOpening",
64+
deferred(),
65+
);
66+
}
67+
68+
/**
69+
* Interact with a dialog.
70+
*
71+
* Will throw if `.expectDialog()` was not called before.
72+
* This is so Sino doesn't try to accept/decline a dialog before
73+
* it opens.
74+
*
75+
* @example
76+
* ```js
77+
* // Note that if `.click()` produces a dialog, do not await it.
78+
* await page.expectDialog();
79+
* elem.click();
80+
* await page.dialog(true, "my username is Sinco");
81+
* ```
82+
*
83+
* @param accept - Whether to accept or dismiss the dialog
84+
* @param promptText - The text to enter into the dialog prompt before accepting. Used only if this is a prompt dialog.
85+
*/
86+
public async dialog(accept: boolean, promptText?: string) {
87+
const p = this.#protocol.notifications.get("Page.javascriptDialogOpening");
88+
if (!p) {
89+
throw new Error(
90+
`Trying to accept or decline a dialog without you expecting one. ".expectDialog()" was not called beforehand.`,
91+
);
92+
}
93+
await p;
94+
const method = "Page.javascriptDialogClosed";
95+
this.#protocol.notifications.set(method, deferred());
96+
const body: Protocol.Page.HandleJavaScriptDialogRequest = {
97+
accept,
98+
};
99+
if (promptText) {
100+
body.promptText = promptText;
101+
}
102+
await this.#protocol.send<
103+
Protocol.Page.HandleJavaScriptDialogRequest,
104+
null
105+
>("Page.handleJavaScriptDialog", body);
106+
const closedPromise = this.#protocol.notifications.get(method);
107+
await closedPromise;
108+
}
109+
49110
/**
50111
* Closes the page. After, you will not be able to interact with it
51112
*/

tests/server.ts

+17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ class PopupsResource extends Drash.Resource {
2323
}
2424
}
2525

26+
class DialogsResource extends Drash.Resource {
27+
public paths = ["/dialogs"];
28+
29+
public GET(_r: Drash.Request, res: Drash.Response) {
30+
return res.html(`
31+
<button type="button" id="button">Click</button>
32+
<script>
33+
document.querySelector('#button').addEventListener('click', e => {
34+
const value = prompt("some text");
35+
e.target.textContent = value;
36+
})
37+
</script>
38+
`);
39+
}
40+
}
41+
2642
class FileInputResource extends Drash.Resource {
2743
public paths = ["/file-input"];
2844

@@ -69,6 +85,7 @@ export const server = new Drash.Server({
6985
PopupsResource,
7086
WaitForRequestsResource,
7187
FileInputResource,
88+
DialogsResource,
7289
],
7390
protocol: "http",
7491
port: 1447,

tests/unit/page_test.ts

+47
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,53 @@ for (const browserItem of browserList) {
268268
});
269269
});
270270

271+
await t.step("dialog()", async (t) => {
272+
await t.step(`Accepts a dialog`, async () => {
273+
const { browser, page } = await buildFor(browserItem.name);
274+
server.run();
275+
await page.location(server.address + "/dialogs");
276+
const elem = await page.querySelector("#button");
277+
page.expectDialog();
278+
elem.click();
279+
await page.dialog(true, "Sinco 4eva");
280+
const val = await page.evaluate(
281+
`document.querySelector("#button").textContent`,
282+
);
283+
await browser.close();
284+
await server.close();
285+
assertEquals(val, "Sinco 4eva");
286+
});
287+
await t.step(`Throws if a dialog was not expected`, async () => {
288+
const { browser, page } = await buildFor(browserItem.name);
289+
let errMsg = "";
290+
try {
291+
await page.dialog(true, "Sinco 4eva");
292+
} catch (e) {
293+
errMsg = e.message;
294+
}
295+
await browser.close();
296+
assertEquals(
297+
errMsg,
298+
'Trying to accept or decline a dialog without you expecting one. ".expectDialog()" was not called beforehand.',
299+
);
300+
});
301+
await t.step(`Rejects a dialog`, async () => {
302+
const { browser, page } = await buildFor(browserItem.name);
303+
server.run();
304+
await page.location(server.address + "/dialogs");
305+
const elem = await page.querySelector("#button");
306+
page.expectDialog();
307+
elem.click();
308+
await page.dialog(false, "Sinco 4eva");
309+
const val = await page.evaluate(
310+
`document.querySelector("#button").textContent`,
311+
);
312+
await browser.close();
313+
await server.close();
314+
assertEquals(val, "");
315+
});
316+
});
317+
271318
await t.step("waitForRequest()", async (t) => {
272319
await t.step(`Should wait for a request via JS`, async () => {
273320
const { browser, page } = await buildFor(browserItem.name);

0 commit comments

Comments
 (0)