Skip to content

Commit

Permalink
Merge pull request #44 from kartAI/feat/20-cadaid-results-page
Browse files Browse the repository at this point in the history
Feat/20 cadaid results page
  • Loading branch information
SverreNystad authored Oct 23, 2024
2 parents 5b86869 + 41fddc6 commit 76dbe42
Show file tree
Hide file tree
Showing 25 changed files with 654 additions and 7 deletions.
154 changes: 154 additions & 0 deletions frontend/cypress/e2e/pages/cadaidPage.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
describe("CadaidPage E2E Tests", () => {
const ROUTES = ["for-soknad/cadaid", "for-soknad/cadaid"];

ROUTES.forEach((route) => {
describe(`Route: ${route}`, () => {
beforeEach(() => {
// Visit the CADAiD page before each test
cy.visit(`/${route}`);
});

it("should upload files successfully and display results", () => {
// Intercept the upload API and mock the response
cy.intercept("POST", "http://localhost:5001/detect/", {
statusCode: 200,
fixture: "mockResponses/detections.json",
}).as("uploadFiles");

// Upload sample1.pdf and sample2.png
cy.get('input[type="file"]').attachFile([
"files/sample1.pdf",
"files/sample2.png",
]);

// Wait for the upload API call
cy.wait("@uploadFiles").its("response.statusCode").should("eq", 200);

// Check if files are listed in FileList using data-cy
cy.get("[data-cy=file-list]").within(() => {
cy.contains("sample1.pdf").should("be.visible");
cy.contains("sample2.png").should("be.visible");
});

// Check if results are displayed
cy.contains("Resultater fra CADAiD").should("be.visible");
cy.contains("plantegning").should("be.visible");
cy.contains("fasade").should("be.visible");
cy.contains("situasjonskart").should("be.visible");
cy.contains("snitt").should("not.exist"); // Assuming 'snitt' is missing
});

it("should handle upload errors gracefully", () => {
// Handle uncaught exceptions to prevent Cypress from failing the test
cy.on("uncaught:exception", (err, runnable) => {
return false; // Prevents Cypress from failing the test
});

// Intercept the upload API and mock an error response
cy.intercept("POST", "http://localhost:5001/detect/", {
statusCode: 500,
fixture: "mockResponses/error.json",
}).as("uploadFilesError");

// Upload invalid.txt
cy.get('input[type="file"]').attachFile("files/invalid.txt");

// Instead of waiting for the network request, check for the validation error
cy.contains("Invalid file type.").should("be.visible");
});

it("should remove a file and its results", () => {
// Intercept the upload API and mock the response
cy.intercept("POST", "http://localhost:5001/detect/", {
statusCode: 200,
fixture: "mockResponses/detections.json",
}).as("uploadFiles");

// Upload sample1.pdf
cy.get('input[type="file"]').attachFile("files/sample1.pdf");

// Wait for the upload API call
cy.wait("@uploadFiles").its("response.statusCode").should("eq", 200);

// Ensure the file is listed
cy.contains("sample1.pdf").should("be.visible");

// Remove the file
cy.contains("sample1.pdf")
.parent("li")
.within(() => {
cy.get('button[aria-label="Remove file sample1.pdf"]').click();
});

// Ensure the file is removed from the list
cy.contains("sample1.pdf").should("not.exist");

// Ensure the corresponding result is removed
cy.contains("plantegning").should("not.exist");
});

it("should display file preview when a file is selected", () => {
// Intercept the upload API and mock the response
cy.intercept("POST", "http://localhost:5001/detect/", {
statusCode: 200,
fixture: "mockResponses/detections.json",
}).as("uploadFiles");

// Upload sample1.pdf
cy.get('input[type="file"]').attachFile("files/sample1.pdf");

// Wait for the upload API call
cy.wait("@uploadFiles").its("response.statusCode").should("eq", 200);

// Select the file from the dropdown
cy.get('select[aria-label="Select file to preview"]').select(
"sample1.pdf",
);

// Check if the FilePreview component displays the selected file
cy.contains("Preview of: sample1.pdf").should("be.visible");
cy.contains("File content would be shown here (mock).").should(
"be.visible",
);
});

it("should validate file types before uploading", () => {
// Handle uncaught exceptions to prevent Cypress from failing the test
cy.on("uncaught:exception", (err, runnable) => {
return false; // Prevents Cypress from failing the test
});

// Attempt to upload invalid.txt
cy.get('input[type="file"]').attachFile("files/invalid.txt");

// Check that the file is not added to the FileList
cy.contains("invalid.txt").should("not.exist");

// Check for a validation error message
cy.contains("Invalid file type.").should("be.visible");
});

it("should be responsive on mobile devices", () => {
// Set viewport to mobile size
cy.viewport("iphone-6");

// Ensure that the main container has flex-direction column
cy.get("[data-cy=main-container]")
.should("have.class", "flex-col")
.and("not.have.class", "flex-row");

// Check that left and right columns are full width
cy.get("[data-cy=left-column]").should("have.css", "width", "100%");
cy.get("[data-cy=right-column]").should("have.css", "width", "100%");

// Check if elements stack vertically
cy.get("h2").should("have.css", "margin-bottom", "1rem"); // mb-4
cy.get('select[aria-label="Select file to preview"]').should(
"have.css",
"width",
"100%",
);
});
});
});
});
1 change: 1 addition & 0 deletions frontend/cypress/fixtures/files/invalid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Failed to upload files
Binary file added frontend/cypress/fixtures/files/sample1.pdf
Binary file not shown.
Binary file added frontend/cypress/fixtures/files/sample2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions frontend/cypress/fixtures/mockResponses/detections.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"file_name": "sample1.pdf",
"drawing_type": ["plantegning"]
},
{
"file_name": "sample2.png",
"drawing_type": ["fasade", "situasjonskart"],
"scale": "1:200",
"room_names": "Bedroom, Bathroom",
"cardinal_direction": "East"
}
]
3 changes: 3 additions & 0 deletions frontend/cypress/fixtures/mockResponses/error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"message": "Failed to upload files"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 3 additions & 1 deletion frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/// <reference types="cypress" />
import "cypress-axe";
import "cypress-file-upload";
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
Expand Down Expand Up @@ -34,4 +36,4 @@
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
// }
36 changes: 36 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"ntnu-kpro-ai-assistant": "file:",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"server-only": "^0.0.1",
"shadcn-ui": "^0.9.2",
"superjson": "^2.2.1",
Expand All @@ -58,6 +59,8 @@
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"cypress": "^13.15.0",
"cypress-axe": "^1.5.0",
"cypress-file-upload": "^5.0.8",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.4",
"jest": "^29.7.0",
Expand Down
110 changes: 110 additions & 0 deletions frontend/src/app/_components/CADAiD.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"use client";

import React, { useState } from 'react';
import type { Detection } from '../../types/detection';
import FileList from './FileList';
import Results from './Results';
import FilePreview from './FilePreview';

async function fetchDetection(formData: FormData): Promise<Detection[]> {
const response = await fetch('http://localhost:5001/detect/', {
method: 'POST',
body: formData,
})

if (!response.ok) {
throw new Error('Failed to upload files');
}

return response.json() as Promise<Detection[]>;
}

const CadaidPage: React.FC = () => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [files, setFiles] = useState<File[]>([]);
const [results, setResults] = useState<Detection[]>([]);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

/**
* Handles file upload by sending selected files to the backend.
* @param event - The file input change event.
*/
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
const uploadedFiles = Array.from(event.target.files);
if (uploadedFiles.length === 0) {
setErrorMessage('No files selected');
return;
}

const ALLOWED_FILE_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];
const invalidFiles = uploadedFiles.filter(file => !ALLOWED_FILE_TYPES.includes(file.type));
if (invalidFiles.length > 0) {
setErrorMessage('Invalid file type.');
return;
}

setIsLoading(true);
setFiles((prevFiles) => [...prevFiles, ...uploadedFiles]);

const formData = new FormData();
uploadedFiles.forEach((file) => {
formData.append('uploaded_files', file);
});

try {
const detections = await fetchDetection(formData);
setResults((prevResults) => [...prevResults, ...detections]);
} catch (error) {
console.error(error);
setErrorMessage('An error occurred while uploading files.');
} finally {
setIsLoading(false); // Ensure loading state is reset
}
}
};

/**
* Removes a file and its corresponding result from the state.
* @param fileName - The name of the file to remove.
*/
const handleFileRemove = (fileName: string) => {
setFiles((prevFiles) => prevFiles.filter(file => file.name !== fileName));
setResults((prevResults) => prevResults.filter(result => result.file_name !== fileName));
};

return (
<div className="p-6 min-h-screen flex flex-col md:flex-row" data-cy="main-container">
{/* Left Column */}
<div className="w-full md:w-1/3 md:pr-4" data-cy="left-column">
<FileList files={files} onRemove={handleFileRemove} onUpload={handleFileUpload}/>

{isLoading && (
<div className="mb-4 flex justify-center items-center">
<div className="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500"></div>
</div>
)}

{errorMessage && (
<div
className="mb-4 p-2 text-red-700 bg-red-100 rounded"
role="alert"
aria-live="assertive"
data-cy="submission-validation"
>
{errorMessage}
</div>
)}

<Results results={results} />
</div>

{/* Right Column */}
<div className="w-full md:w-2/3" data-cy="right-column">
<FilePreview files={files} />
</div>
</div>
);
};

export default CadaidPage;
Loading

0 comments on commit 76dbe42

Please sign in to comment.