Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4870be4
Fixed signup to conform to cucumber acceptance.
LTWaffles86 Mar 17, 2026
0fa77d5
Update cucumber features -PC
Purple-Chicken Mar 31, 2026
71f83b1
Add Jasmine chat acceptance tests -PC
Purple-Chicken Mar 31, 2026
0fa23ef
Overhaul of cucumber chat features.
Purple-Chicken Mar 31, 2026
8b377db
expiration feature and markdown feature in cucumber -PC
Purple-Chicken Mar 31, 2026
c253152
Update acceptance specs to reflect chat api -PC
Purple-Chicken Mar 31, 2026
62f8c35
Add preliminary step definitions for chat testing -PC
Purple-Chicken Mar 31, 2026
d81377b
update unit tests -PC
Purple-Chicken Mar 31, 2026
4b8f9df
Update feature syntax: given authenticated -> given logged in -PC
Purple-Chicken Mar 31, 2026
14de2b6
Some step definitions -PC
Purple-Chicken Apr 2, 2026
fbbdc28
update saved packages -PC
Purple-Chicken Apr 2, 2026
2755672
Fixed signup to conform to cucumber acceptance.
LTWaffles86 Mar 17, 2026
7fcf418
Update cucumber features -PC
Purple-Chicken Mar 31, 2026
e38c951
Add Jasmine chat acceptance tests -PC
Purple-Chicken Mar 31, 2026
94da153
Overhaul of cucumber chat features.
Purple-Chicken Mar 31, 2026
3d53b4c
expiration feature and markdown feature in cucumber -PC
Purple-Chicken Mar 31, 2026
f5ee1c0
Update acceptance specs to reflect chat api -PC
Purple-Chicken Mar 31, 2026
bc30a17
Add preliminary step definitions for chat testing -PC
Purple-Chicken Mar 31, 2026
aba64b1
update unit tests -PC
Purple-Chicken Mar 31, 2026
13913d1
Update feature syntax: given authenticated -> given logged in -PC
Purple-Chicken Mar 31, 2026
b622a98
Some step definitions -PC
Purple-Chicken Apr 2, 2026
81d5c7f
update saved packages -PC
Purple-Chicken Apr 2, 2026
48b588d
Merge remote-tracking branch 'origin/testing' into testing
Purple-Chicken Apr 2, 2026
2adf214
fix ts errors in acceptance tests -PC
Purple-Chicken Apr 6, 2026
e21c0ee
Merge branch 'master' into testing
Purple-Chicken Apr 10, 2026
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
7 changes: 5 additions & 2 deletions features/account_registration.feature
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
Feature: User can create a new account
Scenario: Successful account registration
Given I am on the landing page
When I click "Sign Up"
When I click "Log In"
And I click "Sign Up"
Then I should be on the Create Account page
When I fill in "Username" with "user.example"
And I fill in "Password" with "Password"
And I click "Create Account"
Then I should be on the Login page
And I click "Log In"
Then I should be logged in
And I should see my profile
And I should be redirected to my chat page
23 changes: 23 additions & 0 deletions features/chat_delete.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Feature: Delete Chat
Scenario: Unsuccessful deletion for unauthenticated user
Given I am not logged in
When I request to delete a chat
Then I am returned an error
And the chat is not deleted
Scenario: Unsuccessful deletion for incorrect user
Given I am logged in
When I request to delete a chat
And the chat is owned by a different user
Then I am returned an error
And the chat is not deleted
Scenario: Successful deletion for correct authenticated user
Given I am logged in
When I request to delete a chat
And the chat is owned by my current user
Then I should see a visual change
And the full chat history is deleted
Scenario: Successful deletion of a specific message branch
Given I am logged in
When I delete a specific message within a chat
Then that message and all its subsequent replies (children) are removed
And the rest of the chat history is preserved
7 changes: 7 additions & 0 deletions features/chat_expiration.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: Chat Expiration Logic
Scenario: Attempting to access an expired chat via direct URL
Given a persistent chat was set to expire at "12:00 PM"
And the current time is "12:01 PM"
When I attempt to navigate to that chat's specific URL
Then I should see a "404 - Conversation Expired" message
And the chat should be removed from my sidebar list
25 changes: 25 additions & 0 deletions features/chat_history.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Feature: Chat History Sidebar
Scenario: Authenticated user views their conversation list
Given I am logged in
And I have 3 existing persistent conversations
And I have 1 temporary conversation
When I open the "History" sidebar
Then I should see exactly 3 conversation entries
And each entry should display the chat title and the model used (e.g., "GPT-4")
And the temporary conversation should not be in the list
Scenario: Expired chats are removed from history
Given I have a chat that has reached its expiration time
When I refresh my conversation list
Then the expired chat should no longer appear in the sidebar
Scenario: Switching between conversations
Given I am viewing "Chat A"
When I select "Chat B" from the history sidebar
Then the URL should update to include the ID for "Chat B"
And the message window should clear and load the history for "Chat B"
And the input area should still be locked to the model assigned to "Chat B"

Scenario: Resuming a branched conversation
Given I have a chat with multiple branches
When I select that chat from my history
Then the UI should load the "primary" or most recent branch by default
And I should see an indicator that other branches exist
34 changes: 34 additions & 0 deletions features/chat_markdown.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Feature: Markdown and Emoji Rendering
Scenario Outline: Markdown elements render correctly in chat bubbles
Given I am in a conversation with an LLM
When the <sender> sends a message with <markdown_type>
Then the message should be rendered with the correct <html_element>
And it should have the <visual_style>

Examples:
| sender | markdown_type | html_element | visual_style |
| user | "### Header" | "h3" | "large font" |
| assistant | "- Item 1" | "li" | "bullet point" |
| assistant | "```js code ```" | "pre" | "monospace" |
Scenario: LLM response contains a formatted data table
Given I am logged in
And I have an active conversation
When the LLM generates a response with a GFM table:
"""
| Model | Speed | Accuracy |
|-------|-------|----------|
| GPT-4 | Fast | High |
| Pro | Med | Med |
"""
Then the message should be displayed with a "table" element
And the table should have 1 "thead" row and 2 "tbody" rows
And the first header cell should contain "Model"
And the last body cell should contain "Med"
Scenario: LLM response contains a functional hyperlink
Given I am logged in
And I have an active conversation
When the LLM generates a response with "[Google](https://google.com)"
Then the message should contain a visually distinct link
And the link text should be "Google"
And the link "href" attribute should be "https://google.com"
And the link should open in a new browser tab
32 changes: 32 additions & 0 deletions features/chat_new.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Feature: Create new chats
Scenario: Authenticated user creates first chat
Given I am logged in
And I have no previous chats
When I type a string
And press "Enter"
Then a new chat is created
And the message is displayed
And the message is sent to the server
And a new chat session is created
And the server returns the LLM response
Scenario: Authenticated user creates new chat from a different page
Given I am logged in
And I am currently not on the 'new chat' page
When I select "new chat"
Then I am redirected to the "new chat" page
And I am able to create a new chat
Scenario: User authenticates and creates a new chat
Given I am not logged in
And I have an account
When I sign in to my account
Then I should be redirected to the "new chat" version of the chat page
Scenario: New chat is added to history immediately
Given I am logged in
When I start a new non-temporary chat
Then the chat should immediately appear at the top of my history sidebar
And it should be marked as the "active" conversation
Scenario: User sets an expiration period for a persistent chat
Given I am creating a new persistent chat
When I set the expiration to "24 hours"
And I send my first message
Then the chat is saved to the database with a deletion timestamp
7 changes: 7 additions & 0 deletions features/chat_streaming.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: Real-time Message Streaming
Scenario: User sees incremental response
Given I am logged in
And I have sent a prompt to the LLM
When the server begins streaming the response chunks
Then I should see the message text appearing character-by-character
And I should see a "typing" or "processing" indicator until the stream ends
25 changes: 25 additions & 0 deletions features/chat_tmp.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Feature: Temporary Chats
Scenario: Authenticated user changes mode to temporary chat
Given I am logged in
When I select "temporary chat"
Then an icon changes indicating the new chat will be a temporary chat
Scenario: Authenticated user starts a temporary chat
Given I am logged in
And I have selected "temporary chat"
When I type a string
And press "Enter"
Then my message should be sent
And I should receive a response
And the chat should be a temporary chat
Scenario: Temporary chat is destroyed upon navigation
Given I am in an active "Temporary Chat" session
And I have received 2 messages from the LLM
When I navigate to a different chat or page
And I navigate back to the "New Chat" page
Then the previous temporary messages should be gone
And the chat interface should be reset to empty
Scenario: Temporary chat is destroyed upon page refresh
Given I am in an active "Temporary Chat" session
When I refresh the browser tab
Then the app should load the default "New Chat" state
And no trace of the temporary session should remain in memory
4 changes: 2 additions & 2 deletions features/session.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Feature: User login is remembered via cookies
Feature: User login is remembered via JWT Tokens
Scenario: Returning user is auto-authenticated
Given I have previously logged in
And my session cookie is still valid
And my token is still valid
When I navigate to the home page
Then I should be automatically logged in
And I should be redirected to my dashboard
Expand Down
23 changes: 23 additions & 0 deletions features/step_definitions/account.steps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Given, When, Then } from '@cucumber/cucumber';

When('I enter incorrect credentials', () => {
// Mock entering a wrong password
globalThis.lastError = "Invalid Credentials";
});

When('I enter correct confirmation credentials', () => {
globalThis.lastError = null;
});

Then('I am returned an error', () => {
if (!globalThis.lastError) throw new Error("Expected an error message but none found");
});

Then('the user is not deleted', () => {
// Logic to verify user still exists in your mock DB
});

Then('the user is deleted', () => {
authState.loggedIn = false;
// Logic to verify user was removed
});
136 changes: 136 additions & 0 deletions features/step_definitions/chat.steps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Given, When, Then } from '@cucumber/cucumber';
import { handleRoute } from '../../src/router.ts';

// --- State Management ---
let currentChat = {
id: null,
messages: [],
isTemporary: false,
model: 'default',
expiresAt: null
};

let sidebarChats = [];
let appHTML = ''; // Mocking the #app container content

// --- Given Steps ---

Given('I have no previous chats', () => {
sidebarChats = [];
});

Given('I am currently not on the {string} page', (page) => {
globalThis.location.hash = `#/not-${page}`;
});

Given('I have {int} existing persistent conversations', (count) => {
sidebarChats = Array.from({ length: count }, (_, i) => ({
id: `chat-${i}`,
title: `Persistent Chat ${i}`,
model: 'GPT-4'
}));
});

Given('I have 1 temporary conversation', () => {
// In our memory-only model, this exists in current state but isn't in sidebar
currentChat.isTemporary = true;
});

Given('I am in an active {string} session', (mode) => {
currentChat = {
id: mode === 'Temporary Chat' ? 'temp-session' : 'pers-123',
messages: [{ role: 'assistant', content: 'Hi' }, { role: 'assistant', content: 'Hi again' }],
isTemporary: mode === 'Temporary Chat'
};
});

Given('a persistent chat was set to expire at {string}', (time) => {
currentChat.expiresAt = time;
});

// --- When Steps ---

When('I type a string', () => {
// Mock input interaction
});

When('press {string}', (key) => {
// Trigger message send logic
currentChat.messages.push({ role: 'user', content: 'New Message' });
});

When('I select {string}', (option) => {
if (option === 'temporary chat') {
currentChat.isTemporary = true;
}
});

When('I navigate to a different chat or page', () => {
// Privacy Logic: Wipe memory-only chat on navigation
if (currentChat.isTemporary) {
currentChat = { id: null, messages: [], isTemporary: false };
}
globalThis.location.hash = '#/other-page';
});

When('I refresh the browser tab', () => {
// Total memory wipe
currentChat = { id: null, messages: [], isTemporary: false };
});

When('I set the expiration to {string}', (timeframe) => {
currentChat.expiresAt = timeframe;
});

When('I open the {string} sidebar', (name) => {
// Logic to mock opening the sidebar UI
appHTML = `<div id="sidebar">${sidebarChats.map(c => `<div>${c.title} - ${c.model}</div>`).join('')}</div>`;
});

// --- Then Steps ---

Then('the chat should be a temporary chat', () => {
if (!currentChat.isTemporary) throw new Error('Expected chat to be temporary');
});

Then('the temporary conversation should not be in the list', () => {
const inSidebar = sidebarChats.some(c => c.id === currentChat.id && currentChat.isTemporary);
if (inSidebar) throw new Error('Temporary chat found in persistent sidebar list');
});

Then('the previous temporary messages should be gone', () => {
if (currentChat.messages.length > 0) throw new Error('Temporary messages persisted after navigation');
});

Then('the chat interface should be reset to empty', () => {
// Check mock DOM state
if (currentChat.id !== null) throw new Error('Chat interface was not reset');
});

Then('the chat is saved to the database with a deletion timestamp', () => {
if (!currentChat.expiresAt || currentChat.isTemporary) {
throw new Error('Chat was not saved with correct persistence/expiration metadata');
}
});

Then('I should see a {string} message', (text) => {
// appHTML is a mock for document.getElementById('app').innerHTML
if (!appHTML.includes(text)) throw new Error(`Expected UI to show: ${text}`);
});

// --- Markdown Rendering Steps ---

Then('the message should be rendered with the correct {string}', (element) => {
// Logic to verify that Markdown string was converted to HTML tag
const lastMsg = currentChat.messages[currentChat.messages.length - 1].content;
// This would typically involve a regex check or a DOM parser check
});

Then('the link {string} attribute should be {string}', (attr, value) => {
// Verification logic for <a href="..."> tags
});

Then('I should see exactly {int} conversation entries', (count) => {
// Check the number of items in the sidebarChats array or the mock HTML
if (sidebarChats.length !== count) throw new Error(`Expected ${count} chats, found ${sidebarChats.length}`);
});
23 changes: 18 additions & 5 deletions features/step_definitions/user_stories.steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ let authState = { loggedIn: false, sessionValid: false };
let formHandlers = { login: null, signup: null };
let inputs = { username: { value: '' }, password: { value: '' } };
let alerts = [];
let storage = {};

const isAuthenticated = () => authState.loggedIn || authState.sessionValid;

Expand All @@ -14,6 +15,7 @@ const installDom = () => {
formHandlers = { login: null, signup: null };
inputs = { username: { value: '' }, password: { value: '' } };
alerts = [];
storage = {};

const makeForm = (type) => ({
addEventListener: (event, handler) => {
Expand Down Expand Up @@ -55,19 +57,30 @@ const installDom = () => {

globalThis.location = globalThis.window.location;
globalThis.alert = (message) => alerts.push(message);
globalThis.localStorage = {
getItem: (key) => storage[key] ?? null,
setItem: (key, value) => {
storage[key] = String(value);
},
removeItem: (key) => {
delete storage[key];
},
clear: () => {
storage = {};
},
};
};

const installFetchMock = () => {
globalThis.fetch = async (url) => {
if (url === '/api/me') {
if (url === '/api/users/me') {
return { ok: isAuthenticated() };
}
if (url === '/api/login') {
if (url === '/api/sessions') {
authState.loggedIn = true;
return { ok: true, json: async () => ({}) };
return { ok: true, json: async () => ({ token: 'test-token' }) };
}
if (url === '/api/signup') {
authState.loggedIn = true;
if (url === '/api/users') {
return { ok: true, json: async () => ({}) };
}
return { ok: false, json: async () => ({}) };
Expand Down
Loading
Loading