Skip to content
Draft
6 changes: 2 additions & 4 deletions datasets/populate
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ curl 'https://storage.googleapis.com/deai-313515.appspot.com/example_training_da

# lungs ultrasound
mkdir -p lus_covid
curl 'https://drive.switch.ch/index.php/s/zM5ZrUWK3taaIly/download' > archive.zip
ln -fs lus_covid DeAI-testimages # redirect top level dir
unzip -u archive.zip
rm archive.zip DeAI-testimages
curl 'https://storage.googleapis.com/deai-313515.appspot.com/lus_covid.tar.gz'|
tar -xz

# wikitext
mkdir -p wikitext
Expand Down
98 changes: 98 additions & 0 deletions webapp/cypress/e2e/datasetInput.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@ import { basicTask, setupServerWith } from "../support/e2e";
// TODO move to components testing
// upstream doesn't yet allow that vuejs/test-utils#2468

function droppedFolder(name: string, filenames: string[]): unknown {
const fileEntry = (filename: string) => ({
isFile: true,
isDirectory: false,
name: filename,
file: (onSuccess: (file: File) => void) =>
onSuccess(
new File([], filename, {
type: filename.endsWith(".png") ? "image/png" : "",
}),
),
});

let read = false;
const directoryEntry = {
isFile: false,
isDirectory: true,
name,
createReader: () => ({
readEntries: (onSuccess: (entries: unknown[]) => void) => {
onSuccess(read ? [] : filenames.map(fileEntry));
read = true;
},
}),
};

return {
items: [{ webkitGetAsEntry: () => directoryEntry }],
files: [],
dropEffect: "none",
};
}

function droppedFile(name: string, type: string): unknown {
const fileEntry = {
isFile: true,
isDirectory: false,
name,
file: (onSuccess: (file: File) => void) =>
onSuccess(new File([], name, { type })),
};

return {
items: [{ webkitGetAsEntry: () => fileEntry }],
files: [],
dropEffect: "none",
};
}

function goToDatasetInputStep() {
cy.visit("/list");
cy.get(".driver-popover-close-btn").click();
Expand Down Expand Up @@ -50,6 +99,55 @@ describe("image dataset input by group", () => {

cy.contains("Number of selected files: 3").should("exist");
});

it("allows to drop a folder of images", () => {
setupServerWith(
basicTask("image", {
LABEL_LIST: ["label"],
IMAGE_H: 100,
IMAGE_W: 100,
}),
);

goToDatasetInputStep();
cy.get("button").contains("group").click();
cy.contains("Drop images or a folder here");

cy.get('[data-testid="drop-image-area"]')
.first()
.trigger("drop", {
dataTransfer: droppedFolder("COVID+", [
"first.png",
"second.png",
".DS_Store",
]),
});

cy.contains("Number of selected files: 2").should("exist");
cy.contains("Ignored 1 file(s) that aren't images").should("exist");
});

it("rejects a dropped file that isn't an image", () => {
setupServerWith(
basicTask("image", {
LABEL_LIST: ["label"],
IMAGE_H: 100,
IMAGE_W: 100,
}),
);

goToDatasetInputStep();
cy.get("button").contains("group").click();

cy.get('[data-testid="drop-image-area"]')
.first()
.trigger("drop", {
dataTransfer: droppedFile("data.xlsx", "application/vnd.ms-excel"),
});

cy.contains("Didn't find any images in what you dropped").should("exist");
cy.contains("Number of selected files").should("not.exist");
});
});

describe("image dataset input by csv", () => {
Expand Down
41 changes: 41 additions & 0 deletions webapp/cypress/e2e/notFound.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defaultTasks } from "@epfml/discojs";

import { setupServerWith } from "../support/e2e.ts";

describe("not-found page", () => {
it("is shown for an unknown task", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/not-a-task");
cy.url().should("eq", `${Cypress.config().baseUrl}not-found`);
cy.contains("404");
});

it("can be navigated away from", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/not-a-task");
cy.contains("404");

// the component of the unknown task is kept alive, it shouldn't redirect anymore
cy.get('aside a[href="/list"]').click();
cy.url().should("eq", `${Cypress.config().baseUrl}list`);
});

it("is not covered by the tutorial when leaving it", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/not-a-task");
cy.contains("404");

// the tutorial starts on the task list and overlays the whole page
cy.get('aside a[href="/list"]').click();
cy.get(".driver-popover");

// going back leaves no overlay swallowing the clicks
cy.go("back");
cy.get(".driver-popover").should("not.exist");
cy.get('aside a[href="/create"]').click();
cy.url().should("eq", `${Cypress.config().baseUrl}create`);
});
});
147 changes: 147 additions & 0 deletions webapp/cypress/e2e/tutorial.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { defaultTasks } from "@epfml/discojs";

import { setupServerWith } from "../support/e2e.ts";

// The element highlighted by each step of the tutorial, in order.
// `undefined` for the steps that only display a centered popover.
const STEPS = [
"#tuto-help-bttn",
undefined,
"#llm_task",
"#tuto-create-bttn",
"#tuto-evaluate-bttn",
"#lus_covid",
undefined,
"#tuto-training-bar",
".tuto-data-desc",
".tuto-example-data",
"#tuto-group-bttn",
".group-data-field",
".tuto-train-dash",
"#train-collab-bttn",
"#train-locally-bttn",
undefined,
"#tuto-evaluate-bttn",
"#tuto-slack-link",
];

function expectStep(index: number): void {
cy.get(".driver-popover-progress-text").should(
"have.text",
`Step ${index + 1} of ${STEPS.length}`,
);
const selector = STEPS[index];
if (selector === undefined) return;
cy.get(selector)
.should("have.class", "driver-active-element")
// the highlighted element has to be rendered: the elements of another
// training step are in the DOM but hidden, hence have no dimension
.and(($element) => {
expect(
$element[0].getBoundingClientRect().height,
`${selector} is displayed`,
).to.be.greaterThan(0);
});
}

describe("tutorial", () => {
it("is shown on the first visit only", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/list");
cy.get(".driver-popover-close-btn").click();

// having been shown is persisted, it shouldn't show up again on a new load
cy.visit("/list");
cy.contains("button", "participate"); // wait for the page to be loaded
cy.get(".driver-popover").should("not.exist");
});

it("can still be started from the sidebar", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/list");
cy.get(".driver-popover-close-btn").click();

cy.get("#tuto-help-bttn").click();
cy.get(".driver-popover");

// the skipped first step isn't reachable by going back
cy.get(".driver-popover-prev-btn").should("be.disabled");
});

it("shows a single popover when started while navigating to the task list", () => {
setupServerWith(defaultTasks.titanic);

// starting from another page navigates to the task list, whose mounting
// shouldn't start the tutorial a second time
cy.visit("/");
cy.get("#tuto-help-bttn").click();

cy.contains(".driver-popover-title", "Welcome to DISCO!");
cy.get(".driver-popover").should("have.length", 1);
});

it("only closes when clicking on the cross", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/list");
cy.get(".driver-popover");

// clicking outside of the popover is most likely a misclick, it shouldn't close
cy.get(".driver-overlay").click({ force: true });
cy.get(".driver-popover");

cy.get(".driver-popover-close-btn").click();
cy.get(".driver-popover").should("not.exist");
});

it("displays the current step and the total number of steps", () => {
setupServerWith(defaultTasks.titanic);

cy.visit("/list");
cy.get(".driver-popover-progress-text").should("contain", "Step 1 of");
cy.get(".driver-popover-next-btn").click();
cy.get(".driver-popover-progress-text").should("contain", "Step 2 of");

cy.get(".driver-popover-close-btn").click();

// the first step is skipped when starting from the sidebar,
// it shouldn't be counted
cy.get("#tuto-help-bttn").click();
cy.contains(".driver-popover-title", "Welcome to DISCO!");
cy.get(".driver-popover-progress-text").should("contain", "Step 1 of");
});
it("can be navigated back and forth", () => {
setupServerWith(defaultTasks.lusCovid, defaultTasks.wikitext);

cy.visit("/list");

// "previous" is disabled on the first step
cy.get(".driver-popover-prev-btn").should("be.disabled");

// go through the whole tutorial
for (let index = 0; index < STEPS.length; index++) {
expectStep(index);
if (index < STEPS.length - 1) cy.get(".driver-popover-next-btn").click();
}
cy.url().should("match", /\/lus_covid$/);

// go back to the beginning
for (let index = STEPS.length - 1; index >= 0; index--) {
expectStep(index);
if (index > 0) cy.get(".driver-popover-prev-btn").click();
}
cy.url().should("match", /\/list$/);

// and go the the end again
for (let index = 0; index < STEPS.length; index++) {
expectStep(index);
cy.get(".driver-popover-next-btn").click();
}

// the last step closes the tutorial and goes back to the task list
cy.get(".driver-popover").should("not.exist");
cy.url().should("match", /\/list$/);
});
});
5 changes: 5 additions & 0 deletions webapp/src/assets/css/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@

--color-heading-light: #334155;
--color-heading-dark: #fff;

/* width of a single content card, the page's content column */
--container-card: 700px;
/* two cards side by side, separated by `cards-gap` at md and above */
--container-cards-2: calc(2 * 700px + 2rem);
}
2 changes: 1 addition & 1 deletion webapp/src/components/dataset_input/DataDescription.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="mx-auto w-full max-w-[700px] space-y-4 md:space-y-8">
<div class="mx-auto w-full max-w-card space-y-4 md:space-y-8">
<DropdownCard>
<template #title> Expected Data Format </template>

Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/dataset_input/DatasetInput.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<IconCard class="justify-self-center mx-auto w-full max-w-[700px]">
<IconCard class="justify-self-center mx-auto w-full max-w-card">
<template #title> Connect your data </template>
<template #icon> <PlugIcon /> </template>

Expand Down
Loading
Loading