Skip to content

Commit 226572f

Browse files
authored
Merge branch 'develop' into nav-dropdown-redirects
2 parents 6838ca4 + 292cb56 commit 226572f

2 files changed

Lines changed: 198 additions & 0 deletions

File tree

e2e/specs/signup.spec.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,108 @@ test.describe('signup and email verification', () => {
102102
page.getByRole('button', { name: 'Sign Up', exact: true })
103103
).toBeDisabled();
104104
});
105+
106+
test('sketch persists and runs after unauthenticated user creates an account', async ({
107+
page
108+
}) => {
109+
// This test does a lot in one flow: type + run a sketch, sign up, then
110+
// run it again. The default 30s is tight for it on a loaded machine.
111+
test.setTimeout(60_000);
112+
113+
const username = `${usernamePrefix()}${Date.now()}`;
114+
const email = `${username}${emailSuffix()}`;
115+
116+
const newCode = [
117+
'function setup() {',
118+
' createCanvas(400, 400);',
119+
'}',
120+
'',
121+
'function draw() {',
122+
' background(220);',
123+
" console.log('hi from sketch');",
124+
' noLoop();',
125+
'}'
126+
].join('');
127+
128+
await page.goto('/');
129+
await dismissCookieBanner(page);
130+
131+
// Run sketch as unauthenticated user
132+
const editor = page.locator('.editor-holder');
133+
await editor.click();
134+
await page.keyboard.press('ControlOrMeta+A');
135+
await page.keyboard.type(newCode, { delay: 5 });
136+
137+
// Required: signing up boots the app fresh, and the editor recovers the
138+
// unsaved sketch from the localStorage backup (see IDEView.jsx). That
139+
// backup is only written by CodeMirror's debounced onChange (1s after
140+
// typing stops) — the Play button's syncFileContent() dispatches to Redux
141+
// but never writes it. Without this wait the sketch is lost on signup.
142+
await page.waitForTimeout(1000);
143+
144+
await page.locator('#play-sketch').click({ force: true });
145+
146+
await expect(
147+
page.locator('iframe[title="sketch preview"]')
148+
).toHaveAttribute('src', /9002/, { timeout: 10_000 });
149+
150+
await expect(
151+
page.locator('.preview-console__messages')
152+
).toContainText('hi from sketch', { timeout: 15_000 });
153+
154+
// Try to save the sketch, which should prompt login
155+
await editor.click();
156+
await page.keyboard.press('Control+S');
157+
158+
await expect(
159+
page.getByText(
160+
'In order to save sketches, you must be logged in. Please Login or Sign Up.'
161+
)
162+
).toBeVisible();
163+
164+
// Click on the signup prompt
165+
await page.locator('a[href="/signup"]').last().click();
166+
await page.waitForURL('**/signup', { timeout: 10_000 });
167+
168+
await expect(page.locator('h2.form-container__title')).toHaveText(
169+
'Sign Up'
170+
);
171+
172+
await page.fill('input#username', username);
173+
await page.fill('input#email', email);
174+
await page.fill('input#password', password());
175+
await page.fill('input#confirmPassword', password());
176+
177+
await expect(page.locator('button[type="submit"]')).toBeEnabled({
178+
timeout: 5_000
179+
});
180+
await page.click('button[type="submit"]');
181+
182+
await page.waitForURL((url) => !url.pathname.endsWith('/signup'), {
183+
timeout: 15_000
184+
});
185+
await expect(page.locator(`text=${username}`).first()).toBeVisible({
186+
timeout: 10_000
187+
});
188+
189+
// The sketch the user wrote while logged out survives the signup:
190+
// they land back in the editor with their code still there.
191+
await expect(page.locator('.editor-holder')).toBeVisible();
192+
// Scoped to the editor — the console has its own CodeMirror input,
193+
// so a bare .cm-content matches two elements.
194+
await expect(
195+
editor.locator('.cm-content')
196+
).toContainText("console.log('hi from sketch')", { timeout: 10_000 });
197+
198+
// ...and still runs as the now-authenticated user
199+
await page.locator('#play-sketch').click({ force: true });
200+
201+
await expect(
202+
page.locator('iframe[title="sketch preview"]')
203+
).toHaveAttribute('src', /9002/, { timeout: 10_000 });
204+
205+
await expect(
206+
page.locator('.preview-console__messages')
207+
).toContainText('hi from sketch', { timeout: 20_000 });
208+
});
105209
});

e2e/specs/unsaved_changes.spec.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import type { Page } from '@playwright/test';
2+
import { test, expect } from '../fixtures';
3+
import { dismissCookieBanner } from '../helpers/cookie-banner';
4+
import { createTestUser, loginAs, TestUser } from '../helpers/auth';
5+
6+
// Renders off state.ide.unsavedChanges (see UnsavedChangesIndicator.jsx).
7+
const unsavedIndicator = (page: Page) =>
8+
page.getByRole('img', { name: 'Sketch has unsaved changes' });
9+
10+
// Covers the unsaved changes warning that appears when navigating away from the editor with unsaved changes.
11+
test.describe('unsaved changes warning', () => {
12+
let testUser: TestUser;
13+
14+
test.beforeAll(async ({ request }) => {
15+
testUser = await createTestUser(request);
16+
});
17+
18+
test.beforeEach(async ({ page }) => {
19+
await page.goto('/login');
20+
await dismissCookieBanner(page);
21+
await loginAs(page, testUser);
22+
23+
const editor = page.locator('.editor-holder');
24+
await editor.click();
25+
await page.keyboard.press('ControlOrMeta+A');
26+
await page.keyboard.type('function setup() {createCanvas(400, 400);}', {
27+
delay: 5
28+
});
29+
await expect(unsavedIndicator(page)).toBeVisible({ timeout: 10_000 });
30+
});
31+
32+
const navigateToSketches = async (page: Page) => {
33+
await page.getByRole('menuitem', { name: testUser.username }).click();
34+
await page.locator('#account-sketches').click();
35+
};
36+
37+
test('warns before navigating away from the editor with unsaved changes', async ({
38+
page
39+
}) => {
40+
let dialogMessage = '';
41+
page.once('dialog', (dialog) => {
42+
dialogMessage = dialog.message();
43+
return dialog.dismiss();
44+
});
45+
46+
await navigateToSketches(page);
47+
48+
expect(dialogMessage).toContain('You have unsaved changes');
49+
});
50+
51+
test('blocks navigation if the warning is dismissed', async ({ page }) => {
52+
const urlBeforeNav = page.url();
53+
page.once('dialog', (dialog) => dialog.dismiss());
54+
55+
await navigateToSketches(page);
56+
57+
expect(page.url()).toBe(urlBeforeNav);
58+
await expect(page.locator('.editor-holder')).toBeVisible();
59+
});
60+
61+
test('allows navigation if the warning is accepted', async ({ page }) => {
62+
page.once('dialog', (dialog) => dialog.accept());
63+
64+
await navigateToSketches(page);
65+
66+
await expect(page).toHaveURL(new RegExp(`/${testUser.username}/sketches`), {
67+
timeout: 10_000
68+
});
69+
});
70+
71+
test('does not appear when a sketch is saved before navigating', async ({
72+
page
73+
}) => {
74+
await page.locator('.editor-holder').click();
75+
await page.keyboard.press('Control+S');
76+
await expect(page.getByText('Sketch saved.')).toBeVisible({
77+
timeout: 10_000
78+
});
79+
await expect(unsavedIndicator(page)).toBeHidden();
80+
81+
let dialogFired = false;
82+
page.once('dialog', (dialog) => {
83+
dialogFired = true;
84+
return dialog.accept();
85+
});
86+
87+
await navigateToSketches(page);
88+
89+
await expect(page).toHaveURL(new RegExp(`/${testUser.username}/sketches`), {
90+
timeout: 10_000
91+
});
92+
expect(dialogFired).toBe(false);
93+
});
94+
});

0 commit comments

Comments
 (0)