feature: Add projects homepage and support for adding new projects

This commit is contained in:
Keith Solomon
2026-04-05 16:58:51 -05:00
parent 239a7eff64
commit 6d923b98b9
10 changed files with 492 additions and 38 deletions
+30 -1
View File
@@ -25,7 +25,6 @@ final class BoardApiTest extends IntegrationTestCase
public function testBoardStateEndpointReturnsProjectState(): void
{
$projectId = $this->createProject();
$response = $this->callApi(
'public/api/board-state.php',
'GET',
@@ -37,6 +36,36 @@ final class BoardApiTest extends IntegrationTestCase
self::assertContains('trash', array_column($response['payload']['columns'], 'id'));
}
/**
* Ensure create-project creates markdown storage and returns the new project state.
*
* @return void
*/
public function testCreateProjectEndpointCreatesProject(): void
{
$token = csrf_token();
$response = $this->callApi(
'public/api/create-project.php',
'POST',
[],
[
'title' => 'Release Planning',
'slug' => 'release-planning',
'body' => 'Track launch work.',
'_token' => $token,
],
['HTTP_X_CSRF_TOKEN' => $token]
);
self::assertSame(200, $response['status']);
self::assertTrue($response['payload']['success']);
self::assertSame('release-planning', $response['payload']['state']['project']['id']);
self::assertSame('/?project=release-planning', $response['payload']['state']['projectUrl']);
self::assertContains('trash', array_column($response['payload']['state']['columns'], 'id'));
self::assertFileExists($this->projectRoot . DIRECTORY_SEPARATOR . 'release-planning' . DIRECTORY_SEPARATOR . 'index.md');
}
/**
* Ensure state-changing endpoints reject missing CSRF tokens.
*
+21 -1
View File
@@ -26,7 +26,6 @@ final class BoardServiceTest extends IntegrationTestCase
{
$projectId = $this->createProject();
$service = new BoardService();
$state = $service->getBoardState($projectId);
self::assertSame(
@@ -38,6 +37,27 @@ final class BoardServiceTest extends IntegrationTestCase
self::assertSame('Demo Project', $state['project']['title']);
}
/**
* Ensure creating a project initializes markdown storage and default board state.
*
* @return void
*/
public function testCreateProjectBuildsInitialBoardState(): void
{
$service = new BoardService();
$state = $service->createProject('Release Planning', 'Track launch work.', 'release-planning');
self::assertSame('release-planning', $state['project']['id']);
self::assertSame('Release Planning', $state['project']['title']);
self::assertSame('Track launch work.', trim((string) $state['project']['body']));
self::assertSame(
['backlog', 'ready', 'in-progress', 'review', 'done', 'trash'],
array_column($state['columns'], 'id')
);
self::assertFileExists($this->projectRoot . DIRECTORY_SEPARATOR . 'release-planning' . DIRECTORY_SEPARATOR . 'index.md');
}
/**
* Ensure a created task is persisted and returned in board state.
*
+9
View File
@@ -1,6 +1,15 @@
const { test, expect } = require('@playwright/test');
test.describe('IronKanban smoke', () => {
test('loads the project dashboard with cards and create form', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Choose a project or start a fresh one' })).toBeVisible();
await expect(page.locator('#project-create-form')).toBeVisible();
await expect(page.locator('.project-card')).toHaveCount(1);
await expect(page.locator('.project-card h3')).toHaveText(['Demo Project']);
});
test('loads the demo board and core UI sections', async ({ page }) => {
await page.goto('/?project=demo-project');