Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cypress.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { defineConfig } from "cypress";

export default defineConfig({
viewportHeight: 1080,
viewportWidth: 1920,
e2e: {
baseUrl: "https://reactive-api-console.vercel.app/",
baseUrl: "http://localhost:5173/", //"https://reactive-api-console.vercel.app/"
supportFile: "cypress/support/e2e.{js,jsx,ts,tsx}",
},
component: {
Expand Down
43 changes: 43 additions & 0 deletions cypress/e2e/api-should-fail.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// <reference types="cypress" />

describe("API Failing Cases", () => {
beforeEach(() => {
cy.visit("/");
});

it("should show error for unsupported command", () => {
cy.get('input[placeholder*="Type a command"]').type("unsupported command");
cy.get("button").contains("Send").click();
cy.contains("No results for").should("be.visible");
});

it("should show error if command is valid but API is disabled", () => {
// Assume Cat Facts is enabled by default, so we disable it first
cy.contains("Cat Facts").click(); // This should disable the API
cy.contains("Cat Facts").parent().not("have.class", "ring-orange-500");
cy.get('input[placeholder*="Type a command"]').type("get cat fact");
cy.get("button").contains("Send").click();
cy.contains("No results for").should("be.visible");
});

it("should show error if network/API is down (network failure)", () => {
cy.intercept("GET", "https://catfact.ninja/fact", {
forceNetworkError: true,
}).as("getCatFactFail");
cy.get('input[placeholder*="Type a command"]').type("get cat fact");
cy.get("button").contains("Send").click();
cy.wait("@getCatFactFail");
cy.contains("Network error").should("be.visible");
});

it("should show error if API returns 500 error", () => {
cy.intercept("GET", "https://catfact.ninja/fact", {
statusCode: 500,
body: {},
}).as("getCatFact500");
cy.get('input[placeholder*="Type a command"]').type("get cat fact");
cy.get("button").contains("Send").click();
cy.wait("@getCatFact500");
cy.contains("Error executing").should("be.visible");
});
});
16 changes: 16 additions & 0 deletions cypress/e2e/special-commands/clear-commands.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// <reference types="cypress" />

describe("Clear Commands", () => {
it("should clear chat when 'clear' command is executed", () => {
cy.visit("/");
// Execute a command to populate chat
cy.get('input[placeholder*="Type a command"]').type("get cat fact");
cy.get("button").contains("Send").click();
cy.contains("✅ Executed: get cat fact").should("be.visible");

cy.get('input[placeholder*="Type a command"]').type("clear");
cy.get("button").contains("Send").click();
// The chat should be cleared, so the executed message should not be visible
cy.contains("✅ Executed: get cat fact").should("not.exist");
});
});
14 changes: 14 additions & 0 deletions cypress/e2e/special-commands/help-command.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference types="cypress" />

describe("Help Commands", () => {
beforeEach(() => {
cy.visit("/");
});

it("should display help information when 'help' command is executed", () => {
cy.get('input[placeholder*="Type a command"]').type("help");
cy.get("button").should("not.be.disabled");
cy.get("button").contains("Send").click();
cy.contains("Available Commands").should("be.visible");
});
});
31 changes: 31 additions & 0 deletions cypress/e2e/special-commands/history-command.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// <reference types="cypress" />

describe("History Commands", () => {
beforeEach(() => {
cy.visit("/");
});

it("should show No history when 'history' command is executed without any history", () => {
// check history
cy.get('input[placeholder*="Type a command"]').type("history");
cy.get("button").contains("Send").click();
cy.contains("No command").should("be.visible");
});

it("should show command history when 'history' command is executed", () => {
// Execute a command to ensure there is history
cy.get('input[placeholder*="Type a command"]').type("get chuck joke");
cy.get("button").contains("Send").click();

cy.get('input[placeholder*="Type a command"]').type("history");
cy.get("button").contains("Send").click();

cy.get('input[placeholder*="Type a command"]').type("get activity");
cy.get("button").contains("Send").click();

// Now check history
cy.get('input[placeholder*="Type a command"]').type("history");
cy.get("button").contains("Send").click();
cy.contains("Command History").should("be.visible");
});
});
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --port 5173",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
Expand All @@ -13,7 +13,8 @@
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage.enabled --coverage.reportsDirectory ./coverage",
"cy:open": "cypress open",
"cy:run": "cypress run"
"cy:run": "cypress run",
"cy:start": "concurrently \"pnpm run dev\" \"cypress run\""
},
"dependencies": {
"@reduxjs/toolkit": "^2.8.2",
Expand Down Expand Up @@ -46,6 +47,7 @@
"react-dom": "^19.1.0",
"typescript": "~5.8.3",
"typescript-eslint": "^8.35.1",
"concurrently": "9.2.0",
"vite": "^6.3.5",
"vitest": "^3.2.4"
}
Expand Down
72 changes: 72 additions & 0 deletions pnpm-lock.yaml

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

24 changes: 11 additions & 13 deletions src/components/molecules/ChatCommand/ChatCommand.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
import { useEffect, useState } from "react";
import { BehaviorSubject, debounceTime, tap } from "rxjs";
import { debounceTime, BehaviorSubject as Subject, tap } from "rxjs";

type ChatCommandProps = {
onSendCommand: (command: string) => void;
};

const messageChange = new BehaviorSubject<string>("");
const messageChange = new Subject<string>("");
const messageChange$ = messageChange.asObservable();

export const ChatCommand = ({ onSendCommand }: ChatCommandProps) => {
const [command, setCommand] = useState("");

const [disabled, setDisabled] = useState<boolean>(true);
useEffect(() => {
const subscription = messageChange$
const subscription$ = messageChange$
.pipe(
debounceTime(500),
tap((value) => {
setCommand(value);
})
tap((value) => setDisabled(!value.trim()))
)
.subscribe();
return () => subscription.unsubscribe();
return () => subscription$.unsubscribe();
}, []);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (command.trim()) {
onSendCommand(command.trim());
messageChange.next("");

if (messageChange.value.trim()) {
onSendCommand(messageChange.value.trim());
(e.target as HTMLFormElement).reset();
setDisabled(true);
}
};

Expand All @@ -45,7 +43,7 @@ export const ChatCommand = ({ onSendCommand }: ChatCommandProps) => {
/>
<button
type="submit"
disabled={!command.trim()}
disabled={disabled}
className="btn-primary disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
>
Send
Expand Down
Loading
Loading