Skip to content

Commit 49b495a

Browse files
Merge pull request #621 from microbit-foundation/import-e2e
Accessibility tree fix, e2e for import
2 parents 19a3288 + 8f4ff73 commit 49b495a

File tree

9 files changed

+112
-49
lines changed

9 files changed

+112
-49
lines changed

src/components/ActionNameCard.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ const ActionNameCard = ({
162162
? { bgColor: "transparent", size: "lg" }
163163
: { bgColor: "gray.25", size: "sm" })}
164164
_placeholder={{ opacity: 0.8, color: "gray.900" }}
165+
aria-label={intl.formatMessage({
166+
id: "action-name-placeholder",
167+
})}
165168
placeholder={intl.formatMessage({
166169
id: "action-name-placeholder",
167170
})}

src/components/EditCodeDialog.tsx

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,16 @@
33
*
44
* SPDX-License-Identifier: MIT
55
*/
6-
import {
7-
Box,
8-
Flex,
9-
Modal,
10-
ModalBody,
11-
ModalContent,
12-
ModalOverlay,
13-
} from "@chakra-ui/react";
6+
import { Flex } from "@chakra-ui/react";
147
import { MakeCodeFrameDriver } from "@microbit/makecode-embed/react";
15-
import { forwardRef, memo, useEffect, useRef } from "react";
8+
import { forwardRef, memo, useEffect } from "react";
169
import { useStore } from "../store";
1710
import Editor from "./Editor";
1811

1912
interface EditCodeDialogProps {}
2013

2114
const EditCodeDialog = forwardRef<MakeCodeFrameDriver, EditCodeDialogProps>(
2215
function EditCodeDialog(_, ref) {
23-
const containerRef = useRef<HTMLDivElement>(null);
2416
const isOpen = useStore((s) => s.isEditorOpen);
2517
const tourStart = useStore((s) => s.tourStart);
2618
useEffect(() => {
@@ -29,39 +21,17 @@ const EditCodeDialog = forwardRef<MakeCodeFrameDriver, EditCodeDialogProps>(
2921
}
3022
}, [isOpen, tourStart]);
3123
return (
32-
<>
33-
<Box
34-
ref={containerRef}
35-
transform={isOpen ? undefined : "translate(-150vw, -150vh)"}
36-
visibility={isOpen ? "visible" : "hidden"}
37-
/>
38-
<Modal
39-
size="full"
40-
isOpen={true}
41-
onClose={() => {}}
42-
closeOnEsc={false}
43-
blockScrollOnMount={false}
44-
portalProps={{
45-
containerRef: containerRef,
46-
}}
47-
>
48-
<ModalOverlay>
49-
<ModalContent>
50-
<ModalBody
51-
p={0}
52-
display="flex"
53-
alignItems="stretch"
54-
flexDir="column"
55-
justifyContent="stretch"
56-
>
57-
<Flex flexGrow="1" flexDir="column" w="100%" bgColor="white">
58-
<Editor ref={ref} style={{ flexGrow: 1 }} />
59-
</Flex>
60-
</ModalBody>
61-
</ModalContent>
62-
</ModalOverlay>
63-
</Modal>
64-
</>
24+
<Flex
25+
w="100%"
26+
h="100%"
27+
bgColor="white"
28+
left={isOpen ? undefined : "-150vw"}
29+
top={isOpen ? undefined : "-150vh"}
30+
visibility={isOpen ? "visible" : "hidden"}
31+
position={isOpen ? undefined : "absolute"}
32+
>
33+
<Editor ref={ref} style={{ flexGrow: 1 }} />
34+
</Flex>
6535
);
6636
}
6737
);

src/deployment/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ export type DeploymentConfigFactory = (
1313
// This is configured via a vite alias, defaulting to ./default
1414
import { default as df } from "theme-package";
1515
import { BoxProps } from "@chakra-ui/react";
16+
import { flags } from "../flags";
1617
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1718
const deploymentFactory: DeploymentConfigFactory = df;
1819
export const deployment = (() => {
1920
const deployment = deploymentFactory(import.meta.env);
20-
if (import.meta.env.DEV) {
21+
if (import.meta.env.DEV || flags.e2e) {
2122
return {
2223
...deployment,
23-
// Sidestep CORS issues in development. See vite.config.ts.
24+
// Sidestep CORS issues in development/e2e. See vite.config.ts.
2425
activitiesBaseUrl: "/microbit-org-proxy/classroom/activities/",
2526
};
2627
}

src/e2e/app/data-samples.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ export class DataSamplesPage {
6363
await expect(this.heading).toBeVisible({ timeout: 10000 });
6464
}
6565

66+
async expectActions(expectedActions: string[]) {
67+
const actionInputs = this.page.getByRole("textbox", {
68+
name: "Name of action",
69+
});
70+
await expect(actionInputs).toHaveCount(expectedActions.length, {
71+
timeout: 10_000,
72+
});
73+
let i = 0;
74+
for (const input of await actionInputs.all()) {
75+
await expect(input).toHaveValue(expectedActions[i++]);
76+
}
77+
}
78+
6679
async trainModel() {
6780
await this.page.getByRole("button", { name: "Train model" }).click();
6881
return new TrainModelDialog(this.page);

src/e2e/app/import-page.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Locator, type Page, expect } from "@playwright/test";
2+
3+
export class ImportPage {
4+
private readonly url: string;
5+
private nameInputField: Locator;
6+
private startSessionBtn: Locator;
7+
8+
constructor(public readonly page: Page) {
9+
this.url = `http://localhost:5173${
10+
process.env.CI ? process.env.BASE_URL : "/"
11+
}`;
12+
13+
this.startSessionBtn = page.getByRole("button", { name: "Start session" });
14+
this.nameInputField = page.getByRole("textbox", { name: "Name" });
15+
}
16+
17+
async gotoSimpleAIExerciseTimer() {
18+
// The import process relies on the e2e flag in this query string
19+
// for the proxy to be used.
20+
const query =
21+
"flag=e2e&id=simple-ai-exercise-timer&project=Project%3A%20Simple%20AI%20exercise%20timer&name=Simple%20AI%20exercise%20timer&editors=makecode";
22+
return this.page.goto(`${this.url}import?${query}`);
23+
}
24+
25+
expectName(expected: string) {
26+
return expect(this.nameInputField).toHaveValue(expected);
27+
}
28+
29+
async startSession() {
30+
// Might still be loading.
31+
await expect(this.startSessionBtn).toBeEnabled({
32+
timeout: 5_000,
33+
});
34+
await this.startSessionBtn.click();
35+
}
36+
37+
async expectOnPage() {
38+
await expect(this.page.getByText("New session setup")).toBeVisible();
39+
await expect(this.nameInputField).toBeVisible();
40+
await expect(this.startSessionBtn).toBeVisible();
41+
}
42+
}

src/e2e/fixtures.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import { DataSamplesPage } from "./app/data-samples";
99
import { TestModelPage } from "./app/test-model-page";
1010
import { NewPage } from "./app/new-page";
1111
import { OpenSharedProjectPage } from "./app/open-shared-project-page";
12+
import { ImportPage } from "./app/import-page";
1213

1314
type MyFixtures = {
1415
homePage: HomePage;
1516
newPage: NewPage;
1617
dataSamplesPage: DataSamplesPage;
1718
testModelPage: TestModelPage;
1819
openSharedProjectPage: OpenSharedProjectPage;
20+
importPage: ImportPage;
1921
};
2022

2123
export const test = base.extend<MyFixtures>({
@@ -36,4 +38,7 @@ export const test = base.extend<MyFixtures>({
3638
openSharedProjectPage: async ({ page }, use) => {
3739
await use(new OpenSharedProjectPage(page));
3840
},
41+
importPage: async ({ page }, use) => {
42+
await use(new ImportPage(page));
43+
},
3944
});

src/e2e/import-project.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* (c) 2024, Micro:bit Educational Foundation and contributors
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
import { test } from "./fixtures";
7+
8+
test.describe("import project (microbit.org case)", () => {
9+
test.beforeEach(async ({ homePage }) => {
10+
await homePage.setupContext();
11+
});
12+
13+
test("confirm and import", async ({ importPage, dataSamplesPage }) => {
14+
await importPage.gotoSimpleAIExerciseTimer();
15+
await importPage.expectOnPage();
16+
await importPage.expectName("Simple AI exercise timer");
17+
await importPage.startSession();
18+
19+
await dataSamplesPage.expectOnPage();
20+
await dataSamplesPage.expectActions(["exercising", "not exercising"]);
21+
});
22+
});

src/flags.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export type Flag =
2020
* Flag to enable redux/zustand dev tools.
2121
*/
2222
| "devtools"
23+
/**
24+
* Flag to enable e2e support.
25+
*/
26+
| "e2e"
2327
/**
2428
* Flag to add a beta warning. Enabled for review and staging site stages.
2529
*/
@@ -52,6 +56,7 @@ const allFlags: FlagMetadata[] = [
5256
{ name: "exampleOptInA", defaultOnStages: ["review", "staging"] },
5357
{ name: "exampleOptInB", defaultOnStages: [] },
5458
{ name: "devtools", defaultOnStages: ["local"] },
59+
{ name: "e2e", defaultOnStages: [] },
5560
{
5661
name: "preReleaseNotice",
5762
defaultOnStages: ["staging"],

src/pages/ImportPage.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ const ImportPage = () => {
3838
const [params] = useSearchParams();
3939
setEditorVersionOverride(params.get("editorVersion") || undefined);
4040
const defaultProjectName = useDefaultProjectName();
41-
const [name, setName] = useState<string>(defaultProjectName);
41+
const [name, setName] = useState<string>(
42+
params.get("name") ?? defaultProjectName
43+
);
4244
const isValidSetup = validateProjectName(name);
4345
const [fetchingProject, setFetchingProject] = useState<boolean>(true);
4446
const [project, setProject] = useState<MakeCodeProject>();
@@ -63,7 +65,6 @@ const ImportPage = () => {
6365
intl
6466
);
6567
setProject(project);
66-
setName(resourceName ?? defaultProjectName);
6768
} catch (e) {
6869
// Log the fetch error, but fallback to new blank session by default.
6970
logging.error(e);
@@ -141,11 +142,12 @@ const ImportPage = () => {
141142
</Text>
142143
)}
143144
<Stack py={2} spacing={5}>
144-
<Heading size="md" as="h2">
145+
<Heading size="md" as="h2" id="name-label">
145146
<FormattedMessage id="name-text" />
146147
</Heading>
147148
<Input
148-
aria-labelledby={nameLabel}
149+
type="text"
150+
aria-labelledby="name-label"
149151
minW="25ch"
150152
value={name}
151153
name={nameLabel}

0 commit comments

Comments
 (0)